[
  {
    "path": ".gitignore",
    "content": "# Byte-compiled / optimized / DLL files\n__pycache__/\n*.py[cod]\n*$py.class\n\n# C extensions\n*.so\n\n# Distribution / packaging\n.Python\nenv/\nbuild/\ndevelop-eggs/\ndist/\ndownloads/\neggs/\n.eggs/\nlib/\nlib64/\nparts/\nsdist/\nvar/\nwheels/\n*.egg-info/\n.installed.cfg\n*.egg\n\n# PyInstaller\n#  Usually these files are written by a python script from a template\n#  before PyInstaller builds the exe, so as to inject date/other infos into it.\n*.manifest\n*.spec\n\n# Installer logs\npip-log.txt\npip-delete-this-directory.txt\n\n# Unit test / coverage reports\nhtmlcov/\n.tox/\n.coverage\n.coverage.*\n.cache\nnosetests.xml\ncoverage.xml\n*.cover\n.hypothesis/\n\n# Translations\n*.mo\n*.pot\n\n# Django stuff:\n*.log\nlocal_settings.py\n\n# Flask stuff:\ninstance/\n.webassets-cache\n\n# Scrapy stuff:\n.scrapy\n\n# Sphinx documentation\ndocs/_build/\n\n# PyBuilder\ntarget/\n\n# Jupyter Notebook\n.ipynb_checkpoints\n\n# pyenv\n.python-version\n\n# celery beat schedule file\ncelerybeat-schedule\n\n# SageMath parsed files\n*.sage.py\n\n# dotenv\n.env\n\n# virtualenv\n.venv\nvenv/\nENV/\n\n# Spyder project settings\n.spyderproject\n.spyproject\n\n# Rope project settings\n.ropeproject\n\n# mkdocs documentation\n/site\n\n# mypy\n.mypy_cache/\n\n# test outputs\nloadtest/test_outputs/\n\n# Emacs\n*#*#\n\n# Node Modules\nnode_modules\n\n# Eclipse\n.project\n\n# Specific azuredeploy.parameters.json files for dev testing (ignored not to expose ssh pub keys)\n.params/\n.vs/\n"
  },
  {
    "path": ".jshintrc",
    "content": "{\n    \"esnext\": true,\n    \"node\": true,\n    \"browser\": true,\n    \"nomen\": false,\n    \"bitwise\": true,\n    \"eqeqeq\": true,\n    \"forin\": true,\n    \"immed\": true,\n    \"latedef\": true,\n    \"newcap\": true,\n    \"noarg\": true,\n    \"noempty\": true,\n    \"nonew\": true,\n    \"plusplus\": true,\n    \"regexp\": true,\n    \"undef\": true,\n    \"unused\": true,\n    \"trailing\": true,\n    \"indent\": 4,\n    \"esnext\": true,\n    \"onevar\": true,\n    \"white\": true,\n    \"quotmark\": \"double\",\n    \"predef\": {\n    }\n}\n\n"
  },
  {
    "path": ".travis.yml",
    "content": "dist: trusty\n\nlanguage: python\n\nnode_js: \"0.12\"\n\npython: \"3.5\"\n\ncache:\n  - directories: node_modules\n  - pip\n\nenv:\n  - PYTHONUNBUFFERED=TRUE\n\ninstall:\n  - npm install # Install task runners for lint checking.\n  - pip install azure-mgmt-subscription azure-mgmt-resource keyring pycurl # Install Azure Python SDK (we only need the sub & the resource manager packages)\n\nbefore_script:\n  - ssh-keygen -q -f azure_moodle_id_rsa -N \"\" # Generate SSH keys to send to deployment\n\nscript:\n  - npm test\n  - ./etc/travis.py\n\n"
  },
  {
    "path": "CONTRIBUTE.md",
    "content": "# Contributing to Moodle on Azure \n\nThe TL;DR version is:\n\n  * We are a community project\n  * We seek to make decisions through community consensus\n  * We prefer debate through gradual improvement through pull requests to endless discussion about the \"perfect\" solution\n  * We are a meritocracy, not a democracy\n  * We welcome all your contributions including but not limited to feature requests, bug-reports, documentation and code\n  \n## How the project is managed\n\nThis project welcomes contributions and suggestions. Our goal is to\nwork on Azure specific tooling for deploying and managing the open\nsource [Moodle](http://moodle.org) learning management system on\nAzure. We do not work on Moodle itself here, instead we work upstream\nas appropriate.\n\nThe short version of how to contribute to this project is \"just do\nit\". Where \"it\" can be defined as any valuable contribution (and to be\nclear, asking questions is a valuable contribution):\n\n  * ask questions\n  * provide feedback\n  * write or update documentation\n  * help new users\n  * recommend the project to others\n  * test the code and report bugs\n  * fix bugs and issue pull requests\n  * give us feedback on required features\n  * write and update the software\n  * create artwork\n  * translate to different languages\n  * anything you can see that needs doing\n\nMost contributions require you to agree to a Contributor License\nAgreement (CLA) declaring that you have the right to, and actually do,\ngrant us the rights to use your contribution. For details, visit\nhttps://cla.microsoft.com.\n\nWhen you submit a pull request, a CLA-bot will automatically determine\nwhether you need to provide a CLA and decorate the PR appropriately\n(e.g., label, comment). Simply follow the instructions provided by the\nbot. You will only need to do this once across all repos using our\nCLA.\n\n## Decision Making\n\nThis is a community project. Decisions are made through consensus\nbuilding. All voices are equal and we welcome input from everyone.\n\nThat said, this is not a democracy. Consensus does not mean everyone\nhas to agree. It merely means that nobody is objecting *and* offering\nan alternative.\n\nWhat this means, in practive, is that she who does the work makes the\ndecisions. We'd rather discuss how to improve imperfect code than\nargue over what would make perfect code. So if you have an objection\nto the way we are doing things issue a pull request.\n\nIn the unlikely event that we cannot reach consensus through consensus\nthen the project maintainers (as identified by their having the admin\nbit on GitHub) will make a judgetment call. But normally their\nrole is to guide the community to consensus action, not to make\ndecisions on bhalf of the community.\n\n## Minimum Bar for Contributions\n\nAs the project matures we will add more thorough testing. It is expected\nthat all contributions pass the currently available suite of tests. If\nthey do not then they will be rejected.\n\nIt is also required that contributions which add features also bring\nat least basic testing of that feature.\n\n## Planning\n\nThis is an open source project. We have a few mantras to ensure\nefficient collaboration, these mostly boil down to ensuring good\nvisibility into the communities goals. These include:\n\n  * If it didn't happen in public, it didn't happen\n  * Scratch your own itch\n  \n### If it didn't happen in public, it didn't happen (aka full transparency)\n\nThe goal of this mantra is to ensure maximum visibility into our\ncommunities work in order to:\n\n  1. Provide an opportunity for community feedback in order to ensure\n     our plans are good\n  2. Provide a clear indication of what will be done, what may be done\n     and what won't be done\n  \nBoth of these goals lead to the second mantra \"Scratch your own itch\".\n\n### Scratch your own itch (aka getting what you want)\n\nThis is an open source project. We welcome feature requests and, as a\ncommunity, we will provide feedback on whether we intend to work on it\nor not. To this end we categories feature requests in one of 4 ways:\n\n  * Priority 0 (will address)\n  * Priority 1 (may address)\n  * Priority 2 (maybe one day)\n  * wontfix (out of scope)\n\nUsing these priorities it is easy for community members to decide\nwhere to spend their time. For example:\n\n  * Priority 0 items are actively being worked on by at least one\n    community member. Others are welcome to contribute as appropriate\n    (reviews are particularly important)\n  * Priority 1 items are seen as important and are likely to be worked\n    on in the short to medium term, but there is no community member\n    active on the project at this time. Community members are welcome\n    to take ownership of these issues and propose a solution that they\n    intend to implement. If the community accepts the proposal then it\n    will become a Priority 0 issue.\n  * Priority 2 items are seen as interesting proposals that are not in\n    conflict with the projects goals but are unlikely to be worked on\n    by any existing communty members. Community members who have a\n    need for these items are strongly encouraged to identify\n    themselves and offer a proposal for a solution. If there is enough\n    support within the existing community this item can become a\n    Priority 0 under your leadership.\n  * Wontfix items are considered out of scope for this project.\n    Community members should seek to solve the problem in different\n    ways. Often this will mean contribution to Moodle itself or a\n    plugin that is external to this community.\n\n## Community roles\n\nThis section outlines roles and responsibilities within the community.\n\n### Users\n\nUsers self-identify by using our software and documentation. Their\nresponsibilities are to benefit from our work, but we welcome\ncontributions from users, such as:\n\n  * Ask questions\n  * Answer questions\n  * Feature requests\n  * Bug reports\n  * Design reviews\n  * Planning reviews\n  * Evangelize the project\n  * and more...\n  \nSome users will become more involved with the project, those users\nbecome Contributors.\n\n### Contributors\n\nContributes self-identify by making longer term commitments to our\nproject. Their responsibilities are to help the project be succesful\nby ensuring that our work matches the needs of our users.\nPossible contributions can include:\n\n  * Everything a User might contribute\n  * Remove blocks for users\n  * Provide design input\n  * Review pull requests\n  * Implement features\n  * Triage questions, feature requests and bug reports\n  * and more...\n  \nSome contributors will become very engaged and therefore become an\nessential part of the community, these contributors will become\nMaintainers.\n\n### Maintainers\n\nWe are fans of efficient processes. Maintainers are people who insert\nthemselves into our process to ensure they run well. The goal is to\nempower our contributors who in turn focus on delighting our users.\nMaintainers contributions may include:\n\n  * Everyting Users and Contributors do\n  * Merge pull requests where appropriate\n  * Seek community consensus where conflict occurs\n  * Remove blocks for contributors\n  * and more...\n\n## Pull requests, Review and Merges\n\nWe like efficient processes. Anyone is welcome to issue pull requests.\nEveryone is encouraged to review pull requests. Maintainers are\nresponsible for merging pull requests but they are not responsible for\nreviews, that is a community wide responsibility.\n\nWe operate under two models of review process as appropriate to each\ncircumstance:\n\n  * Merge then Review (our preferred model)\n  * Review then Merge\n  \n### Merge Then Review\n\nIn the \"merge then review\" model a maintainer will merge the pull\nrequest into with minimal review. Community members are still expected\nto review the code, but it is done after the fact.\n\nThe goal is to get the code into a shared repository as early as\npossible. This allows people, including advanced users, to start\ntesting it. This ensures we have the maximum possible exposure to\ntesting in real scenarios early in the process. Encouragin bug reports\nfrom the whole community ensures we have visibility into breaks as\nearly as possible.\n\nThis model has its risks, however. If a PR is on the critical path or\nit is controversial in some way it is expected that maintainers will\nensure it recieves a thorough review before merging (see next section\non \"Review then Merge\". This decision is at the discretion of the\nmaintainer who first triages the pull request.\n\nShould a mistake be made and a bad merge be performed then it can\noften be easier and faster to fix it under the \"Merge then Review\"\nmodel than it is to provide feedback to the original author and await\na fix from them. Should the mistake have a high impact and/or no easy\nfix is available we simply roll back the merge and provide feedback\nvia the review process.\n\nIt should be noted that this model means that maintainers have the\nright to simply merge their own code and expect others to review it\n*after*. Maintainers are expected to use their best judgement when\nexcercising this priviledge.\n\n### Review Then Merge\n\nWhere a change is on the critical path or it is potentiall\ncontriversial maintainers should request reviews using the GitHub\ntooling. The last reviewer to sign-off on the pull request will merge\nthe pull request.\n\n"
  },
  {
    "path": "Gruntfile.js",
    "content": "var grunt = require('grunt');\nrequire('load-grunt-tasks')(grunt);\n\nvar templates = ['nested/*.json', 'managedApplication/*.json', 'loadtest/*.json', '*.json'];\n\ngrunt.initConfig({\n  jshint: {\n      files: templates,\n      options: {\n          jshintrc: '.jshintrc'\n      }\n  }\n});\ngrunt.registerTask('test', ['jshint']);\n"
  },
  {
    "path": "LICENSE",
    "content": "The MIT License (MIT)\nCopyright (c) Microsoft Corporation\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of this software and \nassociated documentation files (the \"Software\"), to deal in the Software without restriction, \nincluding without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, \nand/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, \nsubject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all copies or substantial \nportions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT \nNOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. \nIN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, \nWHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE \nSOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE."
  },
  {
    "path": "LICENSE-DOCS",
    "content": "﻿Attribution 4.0 International\n\n=======================================================================\n\nCreative Commons Corporation (\"Creative Commons\") is not a law firm and\ndoes not provide legal services or legal advice. Distribution of\nCreative Commons public licenses does not create a lawyer-client or\nother relationship. Creative Commons makes its licenses and related\ninformation available on an \"as-is\" basis. Creative Commons gives no\nwarranties regarding its licenses, any material licensed under their\nterms and conditions, or any related information. Creative Commons\ndisclaims all liability for damages resulting from their use to the\nfullest extent possible.\n\nUsing Creative Commons Public Licenses\n\nCreative Commons public licenses provide a standard set of terms and\nconditions that creators and other rights holders may use to share\noriginal works of authorship and other material subject to copyright\nand certain other rights specified in the public license below. The\nfollowing considerations are for informational purposes only, are not\nexhaustive, and do not form part of our licenses.\n\n     Considerations for licensors: Our public licenses are\n     intended for use by those authorized to give the public\n     permission to use material in ways otherwise restricted by\n     copyright and certain other rights. Our licenses are\n     irrevocable. Licensors should read and understand the terms\n     and conditions of the license they choose before applying it.\n     Licensors should also secure all rights necessary before\n     applying our licenses so that the public can reuse the\n     material as expected. Licensors should clearly mark any\n     material not subject to the license. This includes other CC-\n     licensed material, or material used under an exception or\n     limitation to copyright. More considerations for licensors:\n\twiki.creativecommons.org/Considerations_for_licensors\n\n     Considerations for the public: By using one of our public\n     licenses, a licensor grants the public permission to use the\n     licensed material under specified terms and conditions. If\n     the licensor's permission is not necessary for any reason--for\n     example, because of any applicable exception or limitation to\n     copyright--then that use is not regulated by the license. Our\n     licenses grant only permissions under copyright and certain\n     other rights that a licensor has authority to grant. Use of\n     the licensed material may still be restricted for other\n     reasons, including because others have copyright or other\n     rights in the material. A licensor may make special requests,\n     such as asking that all changes be marked or described.\n     Although not required by our licenses, you are encouraged to\n     respect those requests where reasonable. More_considerations\n     for the public: \n\twiki.creativecommons.org/Considerations_for_licensees\n\n=======================================================================\n\nCreative Commons Attribution 4.0 International Public License\n\nBy exercising the Licensed Rights (defined below), You accept and agree\nto be bound by the terms and conditions of this Creative Commons\nAttribution 4.0 International Public License (\"Public License\"). To the\nextent this Public License may be interpreted as a contract, You are\ngranted the Licensed Rights in consideration of Your acceptance of\nthese terms and conditions, and the Licensor grants You such rights in\nconsideration of benefits the Licensor receives from making the\nLicensed Material available under these terms and conditions.\n\n\nSection 1 -- Definitions.\n\n  a. Adapted Material means material subject to Copyright and Similar\n     Rights that is derived from or based upon the Licensed Material\n     and in which the Licensed Material is translated, altered,\n     arranged, transformed, or otherwise modified in a manner requiring\n     permission under the Copyright and Similar Rights held by the\n     Licensor. For purposes of this Public License, where the Licensed\n     Material is a musical work, performance, or sound recording,\n     Adapted Material is always produced where the Licensed Material is\n     synched in timed relation with a moving image.\n\n  b. Adapter's License means the license You apply to Your Copyright\n     and Similar Rights in Your contributions to Adapted Material in\n     accordance with the terms and conditions of this Public License.\n\n  c. Copyright and Similar Rights means copyright and/or similar rights\n     closely related to copyright including, without limitation,\n     performance, broadcast, sound recording, and Sui Generis Database\n     Rights, without regard to how the rights are labeled or\n     categorized. For purposes of this Public License, the rights\n     specified in Section 2(b)(1)-(2) are not Copyright and Similar\n     Rights.\n\n  d. Effective Technological Measures means those measures that, in the\n     absence of proper authority, may not be circumvented under laws\n     fulfilling obligations under Article 11 of the WIPO Copyright\n     Treaty adopted on December 20, 1996, and/or similar international\n     agreements.\n\n  e. Exceptions and Limitations means fair use, fair dealing, and/or\n     any other exception or limitation to Copyright and Similar Rights\n     that applies to Your use of the Licensed Material.\n\n  f. Licensed Material means the artistic or literary work, database,\n     or other material to which the Licensor applied this Public\n     License.\n\n  g. Licensed Rights means the rights granted to You subject to the\n     terms and conditions of this Public License, which are limited to\n     all Copyright and Similar Rights that apply to Your use of the\n     Licensed Material and that the Licensor has authority to license.\n\n  h. Licensor means the individual(s) or entity(ies) granting rights\n     under this Public License.\n\n  i. Share means to provide material to the public by any means or\n     process that requires permission under the Licensed Rights, such\n     as reproduction, public display, public performance, distribution,\n     dissemination, communication, or importation, and to make material\n     available to the public including in ways that members of the\n     public may access the material from a place and at a time\n     individually chosen by them.\n\n  j. Sui Generis Database Rights means rights other than copyright\n     resulting from Directive 96/9/EC of the European Parliament and of\n     the Council of 11 March 1996 on the legal protection of databases,\n     as amended and/or succeeded, as well as other essentially\n     equivalent rights anywhere in the world.\n\n  k. You means the individual or entity exercising the Licensed Rights\n     under this Public License. Your has a corresponding meaning.\n\n\nSection 2 -- Scope.\n\n  a. License grant.\n\n       1. Subject to the terms and conditions of this Public License,\n          the Licensor hereby grants You a worldwide, royalty-free,\n          non-sublicensable, non-exclusive, irrevocable license to\n          exercise the Licensed Rights in the Licensed Material to:\n\n            a. reproduce and Share the Licensed Material, in whole or\n               in part; and\n\n            b. produce, reproduce, and Share Adapted Material.\n\n       2. Exceptions and Limitations. For the avoidance of doubt, where\n          Exceptions and Limitations apply to Your use, this Public\n          License does not apply, and You do not need to comply with\n          its terms and conditions.\n\n       3. Term. The term of this Public License is specified in Section\n          6(a).\n\n       4. Media and formats; technical modifications allowed. The\n          Licensor authorizes You to exercise the Licensed Rights in\n          all media and formats whether now known or hereafter created,\n          and to make technical modifications necessary to do so. The\n          Licensor waives and/or agrees not to assert any right or\n          authority to forbid You from making technical modifications\n          necessary to exercise the Licensed Rights, including\n          technical modifications necessary to circumvent Effective\n          Technological Measures. For purposes of this Public License,\n          simply making modifications authorized by this Section 2(a)\n          (4) never produces Adapted Material.\n\n       5. Downstream recipients.\n\n            a. Offer from the Licensor -- Licensed Material. Every\n               recipient of the Licensed Material automatically\n               receives an offer from the Licensor to exercise the\n               Licensed Rights under the terms and conditions of this\n               Public License.\n\n            b. No downstream restrictions. You may not offer or impose\n               any additional or different terms or conditions on, or\n               apply any Effective Technological Measures to, the\n               Licensed Material if doing so restricts exercise of the\n               Licensed Rights by any recipient of the Licensed\n               Material.\n\n       6. No endorsement. Nothing in this Public License constitutes or\n          may be construed as permission to assert or imply that You\n          are, or that Your use of the Licensed Material is, connected\n          with, or sponsored, endorsed, or granted official status by,\n          the Licensor or others designated to receive attribution as\n          provided in Section 3(a)(1)(A)(i).\n\n  b. Other rights.\n\n       1. Moral rights, such as the right of integrity, are not\n          licensed under this Public License, nor are publicity,\n          privacy, and/or other similar personality rights; however, to\n          the extent possible, the Licensor waives and/or agrees not to\n          assert any such rights held by the Licensor to the limited\n          extent necessary to allow You to exercise the Licensed\n          Rights, but not otherwise.\n\n       2. Patent and trademark rights are not licensed under this\n          Public License.\n\n       3. To the extent possible, the Licensor waives any right to\n          collect royalties from You for the exercise of the Licensed\n          Rights, whether directly or through a collecting society\n          under any voluntary or waivable statutory or compulsory\n          licensing scheme. In all other cases the Licensor expressly\n          reserves any right to collect such royalties.\n\n\nSection 3 -- License Conditions.\n\nYour exercise of the Licensed Rights is expressly made subject to the\nfollowing conditions.\n\n  a. Attribution.\n\n       1. If You Share the Licensed Material (including in modified\n          form), You must:\n\n            a. retain the following if it is supplied by the Licensor\n               with the Licensed Material:\n\n                 i. identification of the creator(s) of the Licensed\n                    Material and any others designated to receive\n                    attribution, in any reasonable manner requested by\n                    the Licensor (including by pseudonym if\n                    designated);\n\n                ii. a copyright notice;\n\n               iii. a notice that refers to this Public License;\n\n                iv. a notice that refers to the disclaimer of\n                    warranties;\n\n                 v. a URI or hyperlink to the Licensed Material to the\n                    extent reasonably practicable;\n\n            b. indicate if You modified the Licensed Material and\n               retain an indication of any previous modifications; and\n\n            c. indicate the Licensed Material is licensed under this\n               Public License, and include the text of, or the URI or\n               hyperlink to, this Public License.\n\n       2. You may satisfy the conditions in Section 3(a)(1) in any\n          reasonable manner based on the medium, means, and context in\n          which You Share the Licensed Material. For example, it may be\n          reasonable to satisfy the conditions by providing a URI or\n          hyperlink to a resource that includes the required\n          information.\n\n       3. If requested by the Licensor, You must remove any of the\n          information required by Section 3(a)(1)(A) to the extent\n          reasonably practicable.\n\n       4. If You Share Adapted Material You produce, the Adapter's\n          License You apply must not prevent recipients of the Adapted\n          Material from complying with this Public License.\n\n\nSection 4 -- Sui Generis Database Rights.\n\nWhere the Licensed Rights include Sui Generis Database Rights that\napply to Your use of the Licensed Material:\n\n  a. for the avoidance of doubt, Section 2(a)(1) grants You the right\n     to extract, reuse, reproduce, and Share all or a substantial\n     portion of the contents of the database;\n\n  b. if You include all or a substantial portion of the database\n     contents in a database in which You have Sui Generis Database\n     Rights, then the database in which You have Sui Generis Database\n     Rights (but not its individual contents) is Adapted Material; and\n\n  c. You must comply with the conditions in Section 3(a) if You Share\n     all or a substantial portion of the contents of the database.\n\nFor the avoidance of doubt, this Section 4 supplements and does not\nreplace Your obligations under this Public License where the Licensed\nRights include other Copyright and Similar Rights.\n\n\nSection 5 -- Disclaimer of Warranties and Limitation of Liability.\n\n  a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE\n     EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS\n     AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF\n     ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS,\n     IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION,\n     WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR\n     PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS,\n     ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT\n     KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT\n     ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU.\n\n  b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE\n     TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION,\n     NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT,\n     INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES,\n     COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR\n     USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN\n     ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR\n     DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR\n     IN PART, THIS LIMITATION MAY NOT APPLY TO YOU.\n\n  c. The disclaimer of warranties and limitation of liability provided\n     above shall be interpreted in a manner that, to the extent\n     possible, most closely approximates an absolute disclaimer and\n     waiver of all liability.\n\n\nSection 6 -- Term and Termination.\n\n  a. This Public License applies for the term of the Copyright and\n     Similar Rights licensed here. However, if You fail to comply with\n     this Public License, then Your rights under this Public License\n     terminate automatically.\n\n  b. Where Your right to use the Licensed Material has terminated under\n     Section 6(a), it reinstates:\n\n       1. automatically as of the date the violation is cured, provided\n          it is cured within 30 days of Your discovery of the\n          violation; or\n\n       2. upon express reinstatement by the Licensor.\n\n     For the avoidance of doubt, this Section 6(b) does not affect any\n     right the Licensor may have to seek remedies for Your violations\n     of this Public License.\n\n  c. For the avoidance of doubt, the Licensor may also offer the\n     Licensed Material under separate terms or conditions or stop\n     distributing the Licensed Material at any time; however, doing so\n     will not terminate this Public License.\n\n  d. Sections 1, 5, 6, 7, and 8 survive termination of this Public\n     License.\n\n\nSection 7 -- Other Terms and Conditions.\n\n  a. The Licensor shall not be bound by any additional or different\n     terms or conditions communicated by You unless expressly agreed.\n\n  b. Any arrangements, understandings, or agreements regarding the\n     Licensed Material not stated herein are separate from and\n     independent of the terms and conditions of this Public License.\n\n\nSection 8 -- Interpretation.\n\n  a. For the avoidance of doubt, this Public License does not, and\n     shall not be interpreted to, reduce, limit, restrict, or impose\n     conditions on any use of the Licensed Material that could lawfully\n     be made without permission under this Public License.\n\n  b. To the extent possible, if any provision of this Public License is\n     deemed unenforceable, it shall be automatically reformed to the\n     minimum extent necessary to make it enforceable. If the provision\n     cannot be reformed, it shall be severed from this Public License\n     without affecting the enforceability of the remaining terms and\n     conditions.\n\n  c. No term or condition of this Public License will be waived and no\n     failure to comply consented to unless expressly agreed to by the\n     Licensor.\n\n  d. Nothing in this Public License constitutes or may be interpreted\n     as a limitation upon, or waiver of, any privileges and immunities\n     that apply to the Licensor or You, including from the legal\n     processes of any jurisdiction or authority.\n\n\n=======================================================================\n\nCreative Commons is not a party to its public\nlicenses. Notwithstanding, Creative Commons may elect to apply one of\nits public licenses to material it publishes and in those instances\nwill be considered the “Licensor.” The text of the Creative Commons\npublic licenses is dedicated to the public domain under the CC0 Public\nDomain Dedication. Except for the limited purpose of indicating that\nmaterial is shared under a Creative Commons public license or as\notherwise permitted by the Creative Commons policies published at\ncreativecommons.org/policies, Creative Commons does not authorize the\nuse of the trademark \"Creative Commons\" or any other trademark or logo\nof Creative Commons without its prior written consent including,\nwithout limitation, in connection with any unauthorized modifications\nto any of its public licenses or any other arrangements,\nunderstandings, or agreements concerning use of licensed material. For\nthe avoidance of doubt, this paragraph does not form part of the\npublic licenses.\n\nCreative Commons may be contacted at creativecommons.org."
  },
  {
    "path": "README.md",
    "content": "\n# Deploy and Manage a Scalable Moodle Cluster on Azure\n\nThis repository contains guides and [Azure Resource Manager](https://docs.microsoft.com/en-us/azure/azure-resource-manager/resource-group-overview) templates designed to help you deploy and manage a highly available and scalable\n[Moodle](https://moodle.com) cluster on Azure. In addition, the repository contains other useful information relevant to running Moodle on Azure such as a listing of Azure-relevant Moodle plugins and information on how to offer Moodle as a Managed Application on the Azure Marketplace or on an IT Service Catalog.\n\nIf you have an Azure account you can deploy Moodle via the [Azure portal](https://portal.azure.com) using the button below, or you can [deploy Moodle via the\nCLI](docs/Deploy.md). Please note that while you can use an [Azure free account](https://azure.microsoft.com/en-us/free/) to get started depending on which template configuration you choose you will likely be required to upgrade to a paid account.\n\n## Deployment Introduction\n\nIn the table below, we provide a number of default configurations at different scales of operation. These options minimize the configuration you would otherwise need to do manually; these options are essentially \"good practice\" recommendations. Once deployed, you will have full access to the Azure resources and can adjust the deployment to suit your needs. If you would prefer to have full control over all the configuration options at deployment, please refer to [the fully configurable section](#Fully Configurable) right after the Predefined deployment option section.\n\n## SSH Key Requirement\n\nAll of the deployment options require you to provide a valid SSH protocol 2 (SSH-2) RSA public-private key pairs with a minimum length of 2048 bits. Other key formats such as ED25519 and ECDSA are not supported.\n\nIf you are unfamiliar with SSH and SSH keys, read this [article](https://docs.microsoft.com/en-us/azure/virtual-machines/linux/mac-create-ssh-keys) which will explain how to generate a key pair.  You will create a ssh key pair. The public key is copied to the instances via the template. The private key is your identity that you will use to connect to different parts of the service.\n\n## Predefined deployment options\n\nBelow are a list of pre-defined/restricted deployment options based on typical deployment scenarios (i.e. dev/test, production etc.) All configurations are fixed and you just need to pass your ssh public key to the template so that you can log in to the deployed VMs.\n\n| Deployment Type | Description | Launch |\n| --- | --- | ---|\n| Minimal  | This deployment will use NFS, Azure Database for MySQL Flexible Server (Burstable SKU 2 vCores), and smaller autoscale web frontend VM sku (1 core) that'll give faster deployment time (less than 30 minutes) and requires only 2 VM cores currently that'll fit even in a free trial Azure subscription.|[![Deploy to Azure Minimally](http://azuredeploy.net/deploybutton.png)](https://portal.azure.com/#create/Microsoft.Template/uri/https%3A%2F%2Fraw.githubusercontent.com%2FAzure%2FMoodle%2Fmaster%2Fazuredeploy-minimal.json)|\n| Small to Mid-Size | Supporting up to 1000 concurrent users.  This deployment will use NFS (no high availability) and Azure Database for MySQL Flexible Server(General Purpose SKU 8 vCores), without other options like elastic search or redis cache.|[![Deploy to Azure Minimally](http://azuredeploy.net/deploybutton.png)](https://portal.azure.com/#create/Microsoft.Template/uri/https%3A%2F%2Fraw.githubusercontent.com%2FAzure%2FMoodle%2Fmaster%2Fazuredeploy-small2mid-noha.json)|\n|Large size deployment (with high availability)| Supporting more than 2000 concurrent users. This deployment will use Azure Premium Files, Azure Database for MySQL Flexible Server (General Purpose SKU 16 vCores) and redis cache, without other options like elastic search. |[![Deploy to Azure Minimally](http://azuredeploy.net/deploybutton.png)](https://portal.azure.com/#create/Microsoft.Template/uri/https%3A%2F%2Fraw.githubusercontent.com%2FAzure%2FMoodle%2Fmaster%2Fazuredeploy-large-ha.json)|\n| Maximum |This maximal deployment will use Azure Premium Files, Azure Database for MySQL (Business Critical SKU 64 vCores), redis cache, elastic search (3 VMs), and pretty large storage sizes (both data disks and DB).|[![Deploy to Azure Maximally](http://azuredeploy.net/deploybutton.png)](https://portal.azure.com/#create/Microsoft.Template/uri/https%3A%2F%2Fraw.githubusercontent.com%2FAzure%2FMoodle%2Fmaster%2Fazuredeploy-maximal.json)|\n\n**NOTE**: The above deployment templates use hard coded Azure Database for MySQL Flexible Server SKUs for easier configuration and quicker deployment of Moodle workloads. If your deployment fails for any reason, please revert to the fully configurable template where possible and change the Azure Database for MySQL Flexible Server parameters accordingly.\n\n## Fully Configurable\n\nIf you would prefer to configure the deployment right at the start of the process, you use the button below. Please note that this method opens up a large number of parameters to configure and users new to this deployment process may find it overwhelming. It is also very likely you may end up with a deployment configuration that is not optimal to your needs. This method is recommended for power users.\n\n[![Deploy to Azure Fully Configurable](http://azuredeploy.net/deploybutton.png)](https://portal.azure.com/#create/Microsoft.Template/uri/https%3A%2F%2Fraw.githubusercontent.com%2FAzure%2FMoodle%2Fmaster%2Fazuredeploy.json)  [![Visualize](https://raw.githubusercontent.com/Azure/azure-quickstart-templates/master/1-CONTRIBUTION-GUIDE/images/visualizebutton.png)](http://armviz.io/#/?load=https%3A%2F%2Fraw.githubusercontent.com%2FAzure%2FMoodle%2Fmaster%2Fazuredeploy.json)\n\n**NOTE**: Before you deploy your Moodle workloads using a fully configurable template, we suggest reviewing [Azure Database for MySQL Flexible Server](https://learn.microsoft.com/en-us/azure/mysql/flexible-server/) documentation to fully understand the parameters and the options suggested in the parameters to pick the right values for your workload needs.\n\n## Stack Architecture\n\nThis template set deploys the following infrastructure core to your Moodle instance:\n\n- Autoscaling web frontend layer (Nginx for https termination, Varnish for caching, Apache/php or nginx/php-fpm)\n- Private virtual network for frontend instances\n- Controller instance running cron and handling syslog for the autoscaled site\n- [Azure Load balancer](https://azure.microsoft.com/en-us/services/load-balancer/) to balance across the autoscaled instances\n- [Azure Database for MySQL](https://azure.microsoft.com/en-us/services/mysql/) or [Azure Database for PostgreSQL](https://azure.microsoft.com/en-us/services/postgresql/) or [Azure SQL Database](https://azure.microsoft.com/en-us/services/sql-database/)\n- For large production deployments Azure Premium Files is recommended for file storage as it provides higher performance and availability with the addition of metadata-caching [https://learn.microsoft.com/en-us/azure/storage/files/smb-performance#metadata-caching-for-premium-smb-file-shares](https://learn.microsoft.com/en-us/azure/storage/files/smb-performance#metadata-caching-for-premium-smb-file-shares)\n\nThis template set *optionally* configures the following additional infrastructure:\n\n- [Azure Backup](https://azure.microsoft.com/en-us/services/backup/) for Moodle site backups\n- [Azure Blob Storage](https://azure.microsoft.com/en-us/services/storage/blobs/) for ObjectFS (Moodle sitedata)\n- [Azure Application Gateway](https://azure.microsoft.com/en-us/services/application-gateway/) for SSL offloading and WAF\n- [Azure Redis Cache](https://azure.microsoft.com/en-us/services/cache/) instance for Moodle caching\n- [Azure DDoS Protection](https://azure.microsoft.com/en-us/services/ddos-protection/) plan to secure your Moodle site from DDoS attacks\n- [Azure Key Vault](https://azure.microsoft.com/en-us/services/key-vault/) for storing your CA Cert for your Moodle site\n- [Azure Search](https://azure.microsoft.com/en-us/services/search/) instance or three Elasticsearch VMs for HA Global Search in Moodle\n- [Apache Tika](http://tika.apache.org/) VMs for search indexing in Moodle\n\n![network_diagram](images/Moodle-Architecture-PremiumFiles.png \"Diagram of deployed stack\")\n\nThe template also optionally installs plugins that allow Moodle to be integrated with select Azure services (see below for details).\n\n## Useful Moodle plugins for integrating Moodle with Azure Services\n\nThere below is a listing of useful plugins allow Moodle to be integrated with select Azure services:\n\n- [Object File System Plugin*](https://github.com/catalyst/moodle-tool_objectfs) for [Azure Blob Storage](https://azure.microsoft.com/en-us/services/storage/blobs/)\n- [Azure Search Plugin*](https://github.com/catalyst/moodle-search_azure) for [Azure Search](https://azure.microsoft.com/en-us/services/logic-apps/)\n- [Trigger Plugin](https://github.com/catalyst/moodle-tool_trigger) and [Restful Webservice Plugin](https://github.com/catalyst/moodle-webservice_restful) for [Azure Logic Apps](https://azure.microsoft.com/en-us/services/logic-apps/) (requires use of [Moodle Connector](https://github.com/catalyst/azure-connector_moodle) now in development)\n- [Office 365 and Microsoft Entra ID (formerly Azure Active Directory) Plugins for Moodle*](https://github.com/Microsoft/o365-moodle) for [Microsoft Entra ID](https://www.microsoft.com/en-us/security/business/identity-access/microsoft-entra-id/)\n- [Elasticsearch Plugin*](https://github.com/catalyst/moodle-search_elastic)\n\nAt the current time this template allows the optional installation of the plugins above with a * next to them. Please note these plugins can be installed at any time post deployment via Moodle's own [plugin directory](https://moodle.org/plugins/). You can find a list of all Azure relevant plugins in the Moodle plugin directory [here](https://moodle.org/plugins/browse.php?list=set&id=91). You might also choose to follow this list via RSS.\n\n## Moodle as a Managed Application\n\nYou can learn more about how you can offer Moodle as a Managed Application on the Azure Marketplace or on an IT Service Catalog [here](https://github.com/Azure/Moodle/tree/master/managedApplication). This is a great read if you are offering Moodle hosting services today for your customers.\n\n## Observations about the current template\n\nThe template is highly configurable. Full details of the configuration options can be found in our [documentation](https://github.com/Azure/Moodle/tree/master/docs) (more specifically in our [parameters documentation](https://github.com/Azure/Moodle/blob/master/docs/Parameters.md)). The following sections describe observations about the template that you will likely want to review before deploying:\n\n**Scalability** Our system is designed to be highly scalable. To achieve this we provide a Virtual Machine Scaleset for the web tier. This is already configured to scale on high load. However, scaling the VMs is not instantaneous. If you know you will have a high-load situation(e.g. exam, you should manually scale the VMs prior to the event. This can be done through the Azure portal or the CLI. The database is less easily scaled at this point, but it is possible and documented in our [management documentation](https://github.com/Azure/Moodle/blob/master/docs/Manage.md#resizing-your-database).\n\n**SSL** The template fully supports SSL but it is not possible for the template to manage this for you. More information in our [managing certs documentation](https://github.com/Azure/Moodle/blob/master/docs/SslCert.md).\n\n**Moodle PHP Code** The Moodle PHP code is stored on the Controller VM and copied to each front end VM upon deployment and upon request (should you update the Moodle code with your own code). For more information see our [management documentation](https://github.com/Azure/Moodle/blob/master/docs/Manage.md#updating-moodle-codesettings).\n\n**Database** Currently the best performance is achieved with [Azure Database for MySQL](https://azure.microsoft.com/en-us/services/mysql/) and [Azure SQL Database](https://azure.microsoft.com/en-us/services/sql-database/). With [Azure Database for PostgreSQL](https://azure.microsoft.com/en-us/services/postgresql/) we have hit database constraints which caused processes to load up on the frontends until they ran out of memory. It is possible some PostgreSQL tuning might help here. Above pre-configured deployment templates deploy Azure Database for MySQL Flexible Server in a VNet. For configuring Azure Database for MySQL Flexible Server outside a VNet to use firewall-based IP restriction, please use the fully configurable template.\n\n**File Storage** There are two options for file storage (moodledata) - Azure Premium Files or NFS. Azure Premium Files is recommended for production deployments as it provides higher performance and availability with the addition of metadata-caching [https://learn.microsoft.com/en-us/azure/storage/files/smb-performance#metadata-caching-for-premium-smb-file-shares](https://learn.microsoft.com/en-us/azure/storage/files/smb-performance#metadata-caching-for-premium-smb-file-shares). Non-HA NFS is recommended for dev/test deployments.\n\n**Search.** Azure supports running an Elasticsearch cluster, however it does not offer a fully-managed Elasticsearch service, so for those looking for a fully-managed Search service [Azure Search](https://azure.microsoft.com/en-us/services/logic-apps/) is recommended.\n\n**Caching.** While enabling Redis cache can improve performance for a large Moodle site we have not seen it be very effective for small-to-medium size sites. We can likely improve upon this, patches welcome ;-)\n\n**Regions.** Note that not all resources types (such as databases) may be available in your region. You should check the list of [Azure Products by Region](https://azure.microsoft.com/en-us/global-infrastructure/services/) to for local availability.\n\n## Common questions about this Template\n\n1. **Is this template Moodle as IaaS or PaaS?**  While the current template leverages PaaS services such as Redis, Azure Database for MySQL Flexible Server, Azure Database for Postgres, MS SQL etc. the current template offers Moodle as IaaS. Given limitations to Moodle our focus is IaaS for the time being however we would love to be informed of your experience running Moodle as PaaS on Azure (i.e. using [Azure Container Service](https://azure.microsoft.com/en-us/services/container-service/) or [Azure App Service](https://azure.microsoft.com/en-us/services/container-service/)).\n\n2. **The current template uses Ubuntu. Will other Operating Systems such as CentOS or Windows Server be supported in the future?** Unfortunately we only have plans to support Ubuntu at this time. It is highly unlikely that this will change.\n\n3. **What configuration do you recommend for my Moodle site?** The answer is it depends. At this stage we provide some rudimentary t-shirt sized deployment recommendations and we are still building out our load testing tools and methodologies to provide more granularity. With that being said this is an area we are investing heavily in this area and we would love your contributions (i.e. load testing scripts, tools, methodologies etc.).\n\n  If you have an immediate need for guidance for a larger sized deployment, you might want to share some details around your deployment on our [issues page](https://github.com/Azure/Moodle/issues) and we will do our best to respond. Please share as much information about your deployment as possible such as:\n\n    - average number of concurrent users your site will see\n    - maximum level of concurrent/simultaneous users your site needs to support\n    - whether or not HA is needed\n    - any other attributes specific to your deployment (i.e. load balancing across regions etc.)\n\n4. **Did Microsoft build this template alone or with the help of the Moodle community?** We did not build this template alone. We relied on the expertise and guidance of many capable Moodle partners around the world. The initial implementation of the template was done by [Catalyst IT](https://github.com/catalyst).\n\n5. **How does this template relate to other Moodle offerings available on the Azure Marketplace?** It is generally not a good idea to run Moodle as a single VM in a production setting. This template is highly configurable and allows for high availability and redundancy.\n\n6. **How does this template relate to this [Azure Quickstart Template for Moodle](https://github.com/Azure/azure-quickstart-templates/tree/master/application-workloads/moodle/moodle-scalable-cluster-ubuntu)?** This repo is the working repo for the quickstart template. We will be pushing changes from this template to the quickstart template on a regular cadence.\n\n7. **I am already running Moodle on Azure. How does this work benefit me?** We are looking for painpoints from you and the broader Moodle on Azure community that we can help solve. We are also looking to understand where our implementation of Moodle on Azure outperforms or underperforms other implementations such as yours that are out in the wild. If you have observations, performance benchmarks or just general feedback about your experience running Moodle on Azure that you'd like to share we're extremely interested! Load testing is a very big area of focus, so if you have scripts you wouldn't mind contributing please let us know.\n\n8. **Has anyone run this template sucessfully in production?** Yes they have. With that being said, we do not make any performance guarantees about this architecture.\n\n9. **What type of improvements have you succeeded in making** Since we first began this effort we have managed to make great gains, achieving a >2x performance boost from our original configuration by making tweaks to things like where PHP files were stored. Our work is nowhere near over.  \n\n10. **What other Azure services (i.e. [Azure CDN](https://azure.microsoft.com/en-us/services/cdn/), [Azure Media Services](https://azure.microsoft.com/en-us/services/media-services/), [Azure Bot Service](https://azure.microsoft.com/en-us/services/bot-service/) etc.) will you be integrating with when this effort is complete?** It's not clear yet. We'll need your [feedback](https://github.com/Azure/Moodle/issues) to decide.\n\n11. **Why is the database on a public subnet?** At this stage only Azure Database for PostgreSQL do not support being moved to a vnet. As a workaround, we use a firewall-based IP restriction allow access only to the controller VM and VMSS load-balancer IPs.  \n\n12. **Is Azure Database for MySQL Flexible Server deployed in a VNet?** When you leverage one of the pre-defined template options, we automatically deploy your Azure Database for MySQL Flexible Server in VNet for better isolation and greater security, optionally you can choose the fully configurable template to deploy Azure Database for MySQL Flexible Server outside VNet depending on your needs.\n\n13. **How can I help with this effort?** Please see below.\n\n## Automated Testing (Travis CI)\n\nThis repository uses [Travis CI](https://travis-ci.org/) to deliver automated testing.\n\nThe following tests are carried out for every Pull Request and will also run in a Travis CI enabled forked repository:\n\n- **JSON Linting** - All JSON files are linted to ensure they do not contain any syntax errors.\n- **JSON Code Style** - All JSON files are tested to ensure they comply with project code style rules.\n\nThe following tests are carried out as part of the Pull Request merging prior to a contribution being accepted into the release branch:\n\n- **Template Validation** - The template is submitted to Azure to ensure it is correctly formatted and contains valid logic.\n- **Template Build** - The template is submitted to Azure and the stack described in the template is built to ensure a stack is correctly deployed.\n\n### Setting Up Travis CI for Template Build\n\nThe following describes the process required if you want to run the template validation and build steps using your own Travis and Azure accounts.\n\nTo set up the build process, you will need:\n\n- An Azure account or active subscription\n- A fork of this repository linked to Travis CI\n- Access to an installed instance of the Azure CLI\n- A SSH key-pair\n\nThe Travis CI process uses the *Azure CLI Service Principal* login method to authenticate against Azure. The documentation for logging in via a Service Principal can be found here: [https://docs.microsoft.com/en-us/cli/azure/authenticate-azure-cli?view=azure-cli-latest#logging-in-with-a-service-principal](https://docs.microsoft.com/en-us/cli/azure/authenticate-azure-cli?view=azure-cli-latest#logging-in-with-a-service-principal)\n\nBefore you can log in using the Service Principal process you need to create a *Service Principal*. The documentation to create a Service Principal login can be found here: [https://docs.microsoft.com/en-us/cli/azure/create-an-azure-service-principal-azure-cli](https://docs.microsoft.com/en-us/cli/azure/create-an-azure-service-principal-azure-cli)\n\nWhen a Service Principal is created using the Azure CLI a JSON response is returned containing:\n\n- **name** - This is the Service Principal username.\n- **password** - This is the Service Principal password.\n- **tenantId** - This is the Service Principal tenant unique ID.\n\nYou will need these three above values to have Travis and Azure deploy and test your template.\n\nThe next step is to take the above values returned by the Service Principal creation and use them to define *environment variables* in Travis CI.\n\nThe following link shows how to set up per repository environment variables in Travis CI: [https://docs.travis-ci.com/user/environment-variables/#Defining-Variables-in-Repository-Settings](https://docs.travis-ci.com/user/environment-variables/#Defining-Variables-in-Repository-Settings) Using this documentation set up the following three *hidden* environment variables in Travis CI for your fork of this repository.\n\n- **SPNAME** - The value of the *name* parameter returned by the Service Principal create process.\n- **SPPASSWORD** - The value of the *password* parameter returned by the Service Principal create process.\n- **SPTENANT** - The value of the *tenant* parameter returned by the Service Principal create process.\n- **SPSSHKEY** *(default: generate new)*- A public SSH key that you have the corresponding private key for. This is currently not used but is required for the build to be successful.\n- **LOCATION** *(default: southcentralus)*- Location for the test resource group.\n- **RESOURCEGROUP** *(default: azmdl-travis-XXX)*- Name to use for the resource group.\n- **FULLCI_BRANCHES** *(default: master)*- Name of branches (separated by ':') to always run FULL CI (if credentials are provided). Full CI will run a deployment test which will create and use resources from your Azure account.\n\n**NOTE:** You can trigger a full CI test by adding *[full ci]* or *[fullci]* anywhere in the commit message.  \n\n**NOTE:** Make sure you set the environment variables to hidden otherwise they will be exposed publicly at run time.\n\n**NOTE:** As per the Travis CI documentation make sure you have correctly escaped the environment variable values when they are defined.\n\nOnce the environment variables are defined, Travis CI will run the template validate and build steps as part of the test process.\n\n## Contributing\n\nThis project welcomes contributions and suggestions. Our goal is to\nwork on Azure specific tooling for deploying and managing the open\nsource [Moodle](http://moodle.org) learning management system on\nAzure. We do not work on Moodle itself here, instead we work upstream\nas appropriate.\n\nThe short version of how to contribute to this project is \"just do\nit\". Where \"it\" can be defined as any valuable contribution (and to be\nclear, asking questions is a valuable contribution):\n\n- ask questions\n- provide feedback\n- write or update documentation\n- help new users\n- recommend the project to others\n- test the code and report bugs\n- fix bugs and issue pull requests\n- give us feedback on required features\n- write and update the software\n- create artwork\n- translate to different languages\n- anything you can see that needs doing\n\nFor a more detailed discussion of how to contribute see our [Contribution Guide](CONTRIBUTE.md).\n\n## Code of Conduct\n\nThis project has adopted the [Microsoft Open Source Code of\nConduct](https://opensource.microsoft.com/codeofconduct/). For more\ninformation see the [Code of Conduct\nFAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact\n[opencode@microsoft.com](mailto:opencode@microsoft.com) with any\nadditional questions or comments.\n\n## Legal Notices\n\nMicrosoft and any contributors grant you a license to the Microsoft\ndocumentation and other content in this repository under the [Creative\nCommons Attribution 4.0 International Public\nLicense](https://creativecommons.org/licenses/by/4.0/legalcode), see\nthe [LICENSE](LICENSE) file, and grant you a license to any code in\nthe repository under the [MIT\nLicense](https://opensource.org/licenses/MIT), see the\n[LICENSE-CODE](LICENSE-CODE) file.\n\nMicrosoft, Windows, Microsoft Azure and/or other Microsoft products\nand services referenced in the documentation may be either trademarks\nor registered trademarks of Microsoft in the United States and/or\nother countries. The licenses for this project do not grant you rights\nto use any Microsoft names, logos, or trademarks. Microsoft's general\ntrademark guidelines can be found at\n[http://go.microsoft.com/fwlink/?LinkID=254653](http://go.microsoft.com/fwlink/?LinkID=254653).\n\nPrivacy information can be found at [https://privacy.microsoft.com/en-us/](https://privacy.microsoft.com/en-us/)\n\nMicrosoft and any contributors reserve all others rights, whether\nunder their respective copyrights, patents, or trademarks, whether by\nimplication, estoppel or otherwise.\n\n## Next Steps\n\n  1. [Deploy a Moodle Cluster](docs/Deploy.md)\n  1. [Obtain Deployment Details about a Moodle Cluster](docs/Get-Install-Data.md)\n  1. [Delete a Moodle Cluster](docs/Delete.md)\n"
  },
  {
    "path": "SECURITY.md",
    "content": "<!-- BEGIN MICROSOFT SECURITY.MD V0.0.7 BLOCK -->\n\n## Security\n\nMicrosoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/Microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), [Xamarin](https://github.com/xamarin), and [our GitHub organizations](https://opensource.microsoft.com/).\n\nIf you believe you have found a security vulnerability in any Microsoft-owned repository that meets [Microsoft's definition of a security vulnerability](https://aka.ms/opensource/security/definition), please report it to us as described below.\n\n## Reporting Security Issues\n\n**Please do not report security vulnerabilities through public GitHub issues.**\n\nInstead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://aka.ms/opensource/security/create-report).\n\nIf you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com).  If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://aka.ms/opensource/security/pgpkey).\n\nYou should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://aka.ms/opensource/security/msrc). \n\nPlease include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue:\n\n  * Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.)\n  * Full paths of source file(s) related to the manifestation of the issue\n  * The location of the affected source code (tag/branch/commit or direct URL)\n  * Any special configuration required to reproduce the issue\n  * Step-by-step instructions to reproduce the issue\n  * Proof-of-concept or exploit code (if possible)\n  * Impact of the issue, including how an attacker might exploit the issue\n\nThis information will help us triage your report more quickly.\n\nIf you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://aka.ms/opensource/security/bounty) page for more details about our active programs.\n\n## Preferred Languages\n\nWe prefer all communications to be in English.\n\n## Policy\n\nMicrosoft follows the principle of [Coordinated Vulnerability Disclosure](https://aka.ms/opensource/security/cvd).\n\n<!-- END MICROSOFT SECURITY.MD BLOCK -->\n"
  },
  {
    "path": "azuredeploy-large-ha.json",
    "content": "{\n    \"$schema\": \"https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#\",\n    \"contentVersion\": \"1.0.0.0\",\n    \"parameters\": {\n        \"_artifactsLocation\": {\n            \"type\": \"string\",\n            \"metadata\": {\n                \"description\": \"The base URI where artifacts required by this template are located. When the template is deployed using the accompanying scripts, a private location in the subscription will be used and this value will be automatically generated.\"\n            },\n            \"defaultValue\": \"https://raw.githubusercontent.com/Azure/Moodle/master/\"\n        },\n        \"_artifactsLocationSasToken\": {\n            \"type\": \"securestring\",\n            \"metadata\": {\n                \"description\": \"The sasToken required to access _artifactsLocation.  When the template is deployed using the accompanying scripts, a sasToken will be automatically generated.\"\n            },\n            \"defaultValue\": \"\"\n        },\n        \"sshPublicKey\": {\n            \"metadata\": {\n                \"description\": \"ssh public key\"\n            },\n            \"type\": \"string\"\n        },\n        \"mysqlflexHaEnabled\": {\n            \"allowedValues\": [\n                \"Disabled\",\n                \"SameZone\",\n                \"ZoneRedundant\"\n            ],\n            \"defaultValue\": \"Disabled\",\n            \"metadata\": {\n                \"description\": \"High availability mode for azure database for mysql flexible server.\"\n            },\n            \"type\": \"string\"\n        }\n    },\n    \"resources\": [\n        {\n            \"type\": \"Microsoft.Resources/deployments\",\n            \"apiVersion\": \"2017-05-10\",\n            \"name\": \"mainTemplate\",\n            \"properties\": {\n                \"mode\": \"Incremental\",\n                \"parameters\": {\n                    \"_artifactsLocation\":           { \"value\": \"[parameters('_artifactsLocation')]\" },\n                    \"_artifactsLocationSasToken\":   { \"value\": \"[parameters('_artifactsLocationSasToken')]\" },\n                    \"redisDeploySwitch\":            { \"value\": true },\n                    \"sshPublicKey\":                 { \"value\": \"[parameters('sshPublicKey')]\" },\n                    \"autoscaleVmCountMax\":          { \"value\": 20 },\n                    \"autoscaleVmSku\":\t\t        { \"value\": \"Standard_DS3_v2\" },\n                    \"searchType\":                   { \"value\": \"elastic\" },\n                    \"dbServerType\":                 { \"value\": \"mysqlflex\" },\n                    \"vnetDbDeploySwitch\":           { \"value\": true },\n                    \"mysqlflexSkuName\":             { \"value\": \"Standard_D16ds_v4\" },\n                    \"mysqlflexStgIops\":             { \"value\": 5000 },\n                    \"mysqlflexStgSizeGiB\":          { \"value\": 512 },\n                    \"mysqlflexHaEnabled\":           { \"value\": \"[parameters('mysqlflexHaEnabled')]\" },\n                    \"fileServerType\":               { \"value\": \"azurefiles\" },\n                    \"fileServerDiskSize\":           { \"value\": 1024 },\n                    \"storageAccountType\":           { \"value\": \"Premium_LRS\" },\n                    \"loadBalancerSku\":              { \"value\": \"Standard\" },\n                    \"OSDiskSizeInGB\":               { \"value\": 1024 }\n                },\n                \"templateLink\": {\n                    \"uri\": \"[concat(parameters('_artifactsLocation'), 'azuredeploy.json', parameters('_artifactsLocationSasToken'))]\"\n                }\n            }\n        }\n    ],\n    \"outputs\": {\n        \"mainTemplateOutputs\": {\n            \"type\": \"object\",\n            \"value\": \"[reference('mainTemplate').outputs]\"\n        }\n    },\n    \"variables\": {\n        \"documentation01\": \"This wrapper template calls the main-template with pre-defined configs for large size workloads (with high availability) and the only required parameter (sshPublicKey).\",\n        \"documentation02\": \"For the expected small-to-mid size workloads, other parameters are fixed in this template and overridden as above.\"\n    }\n}\n"
  },
  {
    "path": "azuredeploy-maximal.json",
    "content": "{\n    \"$schema\": \"https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#\",\n    \"contentVersion\": \"1.0.0.0\",\n    \"parameters\": {\n        \"_artifactsLocation\": {\n            \"type\": \"string\",\n            \"metadata\": {\n                \"description\": \"The base URI where artifacts required by this template are located. When the template is deployed using the accompanying scripts, a private location in the subscription will be used and this value will be automatically generated.\"\n            },\n            \"defaultValue\": \"https://raw.githubusercontent.com/Azure/Moodle/master/\"\n        },\n        \"_artifactsLocationSasToken\": {\n            \"type\": \"securestring\",\n            \"metadata\": {\n                \"description\": \"The sasToken required to access _artifactsLocation.  When the template is deployed using the accompanying scripts, a sasToken will be automatically generated.\"\n            },\n            \"defaultValue\": \"\"\n        },\n        \"sshPublicKey\": {\n            \"metadata\": {\n                \"description\": \"ssh public key\"\n            },\n            \"type\": \"string\"\n        },\n        \"mysqlflexHaEnabled\": {\n            \"allowedValues\": [\n                \"Disabled\",\n                \"SameZone\",\n                \"ZoneRedundant\"\n            ],\n            \"defaultValue\": \"Disabled\",\n            \"metadata\": {\n                \"description\": \"High availability mode for azure database for mysql flexible server.\"\n            },\n            \"type\": \"string\"\n        }\n    },\n    \"resources\": [\n        {\n            \"type\": \"Microsoft.Resources/deployments\",\n            \"apiVersion\": \"2017-05-10\",\n            \"name\": \"mainTemplate\",\n            \"properties\": {\n                \"mode\": \"Incremental\",\n                \"parameters\": {\n                    \"_artifactsLocation\":           { \"value\": \"[parameters('_artifactsLocation')]\" },\n                    \"_artifactsLocationSasToken\":   { \"value\": \"[parameters('_artifactsLocationSasToken')]\" },\n                    \"redisDeploySwitch\":            { \"value\": true },\n                    \"azureBackupSwitch\":            { \"value\": true },\n                    \"searchType\":                   { \"value\": \"elastic\" },\n                    \"sshPublicKey\":                 { \"value\": \"[parameters('sshPublicKey')]\" },\n                    \"dbServerType\":                 { \"value\": \"mysqlflex\" },\n                    \"vnetDbDeploySwitch\":           { \"value\": true },\n                    \"mysqlflexSkuName\":             { \"value\": \"Standard_E64ds_v4\" },\n                    \"mysqlflexStgIops\":             { \"value\": 10000 },\n                    \"mysqlflexStgSizeGiB\":          { \"value\": 1024 },\n                    \"mysqlflexHaEnabled\":           { \"value\": \"[parameters('mysqlflexHaEnabled')]\" },\n                    \"fileServerType\":               { \"value\": \"azurefiles\" },\n                    \"fileServerDiskSize\":           { \"value\": 1024 },\n                    \"storageAccountType\":           { \"value\": \"Premium_LRS\" },\n                    \"autoscaleVmSku\":\t\t        { \"value\": \"Standard_DS3_v2\" }\n                },\n                \"templateLink\": {\n                    \"uri\": \"[concat(parameters('_artifactsLocation'), 'azuredeploy.json', parameters('_artifactsLocationSasToken'))]\"\n                }\n            }\n        }\n    ],\n    \"outputs\": {\n        \"mainTemplateOutputs\": {\n            \"type\": \"object\",\n            \"value\": \"[reference('mainTemplate').outputs]\"\n        }\n    },\n    \"variables\": {\n        \"documentation01\": \"This wrapper template calls the main-template with maximum configs and the only required parameter (sshPublicKey).\",\n        \"documentation02\": \"For the best-possible performance, highly available, and most Moodle features, other parameters are fixed in this template and overridden as above.\"\n    }\n}\n"
  },
  {
    "path": "azuredeploy-minimal.json",
    "content": "{\n    \"$schema\": \"https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#\",\n    \"contentVersion\": \"1.0.0.0\",\n    \"parameters\": {\n        \"_artifactsLocation\": {\n            \"type\": \"string\",\n            \"metadata\": {\n                \"description\": \"The base URI where artifacts required by this template are located. When the template is deployed using the accompanying scripts, a private location in the subscription will be used and this value will be automatically generated.\"\n            },\n            \"defaultValue\": \"https://raw.githubusercontent.com/Azure/Moodle/master/\"\n        },\n        \"_artifactsLocationSasToken\": {\n            \"type\": \"securestring\",\n            \"metadata\": {\n                \"description\": \"The sasToken required to access _artifactsLocation.  When the template is deployed using the accompanying scripts, a sasToken will be automatically generated.\"\n            },\n            \"defaultValue\": \"\"\n        },\n        \"sshPublicKey\": {\n            \"metadata\": {\n                \"description\": \"ssh public key\"\n            },\n            \"type\": \"string\"\n        }\n    },\n    \"resources\": [\n        {\n            \"type\": \"Microsoft.Resources/deployments\",\n            \"apiVersion\": \"2021-01-01\",\n            \"name\": \"mainTemplate\",\n            \"properties\": {\n                \"mode\": \"Incremental\",\n                \"parameters\": {\n                    \"_artifactsLocation\":           { \"value\": \"[parameters('_artifactsLocation')]\" },\n                    \"_artifactsLocationSasToken\":   { \"value\": \"[parameters('_artifactsLocationSasToken')]\" },\n                    \"redisDeploySwitch\":            { \"value\": false },\n                    \"sshPublicKey\":                 { \"value\": \"[parameters('sshPublicKey')]\" },\n                    \"autoscaleVmSku\":               { \"value\": \"Standard_DS1_v2\" },\n                    \"enableAccelNwForOtherVmsSwitch\":   { \"value\": false },\n                    \"dbServerType\":                 { \"value\": \"mysqlflex\" },\n                    \"vnetDbDeploySwitch\":           {\"value\": true },\n                    \"fileServerDiskCount\":          { \"value\": 2 },\n                    \"fileServerDiskSize\":           { \"value\": 32 }\n                },\n                \"templateLink\": {\n                    \"uri\": \"[concat(parameters('_artifactsLocation'), 'azuredeploy.json', parameters('_artifactsLocationSasToken'))]\"\n                }\n            }\n        }\n    ],\n    \"outputs\": {\n        \"mainTemplateOutputs\": {\n            \"type\": \"object\",\n            \"value\": \"[reference('mainTemplate').outputs]\"\n        }\n    },\n    \"variables\": {\n        \"documentation01\": \"This wrapper template calls the main-template with bare minimum configs and the only required parameter (sshPublicKey).\",\n        \"documentation02\": \"To speed up deployment and consume least resources, other parameters are fixed in this tempalte and overriden as follows:\",\n        \"documentation03\": \"   - fileServerType: nfs\",\n        \"documentation04\": \"   - autoscaleVmSku: Standard_DS1_vs\",\n        \"documentation05\": \"   - fileServerDiskCount: 2\",\n        \"documentation06\": \"   - dbServerType: mysqlflex\",\n        \"documentation07\": \"   - redisDeploySwitch: false\"\n    }\n}\n"
  },
  {
    "path": "azuredeploy-small2mid-noha.json",
    "content": "{\n    \"$schema\": \"https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#\",\n    \"contentVersion\": \"1.0.0.0\",\n    \"parameters\": {\n        \"_artifactsLocation\": {\n            \"type\": \"string\",\n            \"metadata\": {\n                \"description\": \"The base URI where artifacts required by this template are located. When the template is deployed using the accompanying scripts, a private location in the subscription will be used and this value will be automatically generated.\"\n            },\n            \"defaultValue\": \"https://raw.githubusercontent.com/Azure/Moodle/master/\"\n        },\n        \"_artifactsLocationSasToken\": {\n            \"type\": \"securestring\",\n            \"metadata\": {\n                \"description\": \"The sasToken required to access _artifactsLocation.  When the template is deployed using the accompanying scripts, a sasToken will be automatically generated.\"\n            },\n            \"defaultValue\": \"\"\n        },\n        \"sshPublicKey\": {\n            \"metadata\": {\n                \"description\": \"ssh public key\"\n            },\n            \"type\": \"string\"\n        }\n    },\n    \"resources\": [\n        {\n            \"type\": \"Microsoft.Resources/deployments\",\n            \"apiVersion\": \"2017-05-10\",\n            \"name\": \"mainTemplate\",\n            \"properties\": {\n                \"mode\": \"Incremental\",\n                \"parameters\": {\n                    \"_artifactsLocation\":           { \"value\": \"[parameters('_artifactsLocation')]\" },\n                    \"_artifactsLocationSasToken\":   { \"value\": \"[parameters('_artifactsLocationSasToken')]\" },\n                    \"redisDeploySwitch\":            { \"value\": false },\n                    \"sshPublicKey\":                 { \"value\": \"[parameters('sshPublicKey')]\" },\n                    \"dbServerType\":                 { \"value\": \"mysqlflex\" },\n                    \"vnetDbDeploySwitch\":           {\"value\": true },\n                    \"mysqlflexSkuName\":             { \"value\": \"Standard_D8ds_v4\" },\n                    \"mysqlflexStgIops\":             { \"value\": 1000 },\n                    \"mysqlflexStgSizeGiB\":          { \"value\": 128 },\n                    \"fileServerDiskSize\":           { \"value\": 128 }\n                },\n                \"templateLink\": {\n                    \"uri\": \"[concat(parameters('_artifactsLocation'), 'azuredeploy.json', parameters('_artifactsLocationSasToken'))]\"\n                }\n            }\n        }\n    ],\n    \"outputs\": {\n        \"mainTemplateOutputs\": {\n            \"type\": \"object\",\n            \"value\": \"[reference('mainTemplate').outputs]\"\n        }\n    },\n    \"variables\": {\n        \"documentation01\": \"This wrapper template calls the main-template with pre-defined configs for small-to-mid size workloads (without high availability) and the only required parameter (sshPublicKey).\",\n        \"documentation02\": \"For the expected small-to-mid size workloads, other parameters are fixed in this template and overridden as above.\"\n    }\n}\n"
  },
  {
    "path": "azuredeploy.json",
    "content": "{\n    \"$schema\": \"https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#\",\n    \"contentVersion\": \"1.0.0.0\",\n    \"parameters\": {\n        \"_artifactsLocation\": {\n            \"type\": \"string\",\n            \"metadata\": {\n                \"description\": \"The base URI where artifacts required by this template are located. When the template is deployed using the accompanying scripts, a private location in the subscription will be used and this value will be automatically generated.\"\n            },\n            \"defaultValue\": \"https://raw.githubusercontent.com/Azure/Moodle/master/\"\n        },\n        \"_artifactsLocationSasToken\": {\n            \"type\": \"securestring\",\n            \"metadata\": {\n                \"description\": \"The sasToken required to access _artifactsLocation.  When the template is deployed using the accompanying scripts, a sasToken will be automatically generated.\"\n            },\n            \"defaultValue\": \"\"\n        },\n        \"applyScriptsSwitch\": {\n            \"defaultValue\": true,\n            \"metadata\": {\n                \"description\": \"Switch to process or bypass all scripts/extensions\"\n            },\n            \"type\": \"bool\"\n        },\n        \"azureBackupSwitch\": {\n            \"defaultValue\": false,\n            \"metadata\": {\n                \"description\": \"Switch to configure AzureBackup and enlist VM's\"\n            },\n            \"type\": \"bool\"\n        },\n        \"redisDeploySwitch\": {\n            \"defaultValue\": false,\n            \"metadata\": {\n                \"description\": \"Switch to deploy a redis cache or not. Note that certain versions of Moodle (e.g., 3.1) don't work well with Redis, so use this only for known well-working Moodle versions (e.g., 3.4).\"\n            },\n            \"type\": \"bool\"\n        },\n        \"vnetGwDeploySwitch\": {\n            \"defaultValue\": false,\n            \"metadata\": {\n                \"description\": \"Switch to deploy a virtual network gateway or not\"\n            },\n            \"type\": \"bool\"\n        },\n        \"installObjectFsSwitch\": {\n            \"defaultValue\": false,\n            \"metadata\": {\n                \"description\": \"Switch to install Moodle Object FS plugins (with Azure Blob storage)\"\n            },\n            \"type\": \"bool\"\n        },\n        \"installO365pluginsSwitch\": {\n            \"defaultValue\": false,\n            \"metadata\": {\n                \"description\": \"Switch to install Moodle Office 365 plugins. As of May 22, 2018, O365 plugins for Moodle 3.5 haven't been released, so to set this true, you must set the moodleVersion to 3.4 or below.\"\n            },\n            \"type\": \"bool\"\n        },\n        \"installGdprPluginsSwitch\": {\n            \"defaultValue\": false,\n            \"metadata\": {\n                \"description\": \"(Should be used only for Moodle 3.4 & 3.3) Switch to install Moodle GDPR plugins. Note these require Moodle versions 3.4.2+ or 3.3.5+ and these are included by default in Moodle 3.5. So if you choose MOODLE_35_STABLE as your moodleVersion, do not set this to true.\"\n            },\n            \"type\": \"bool\"\n        },\n        \"htmlLocalCopySwitch\": {\n            \"defaultValue\": true,\n            \"metadata\": {\n                \"description\": \"Switch to create a local copy of /moodle/html or not\"\n            },\n            \"type\": \"bool\"\n        },\n        \"ddosSwitch\": {\n            \"defaultValue\": false,\n            \"metadata\": {\n                \"description\": \"Switch to create a DDoS protection plan\"\n            },\n            \"type\": \"bool\"\n        },\n        \"enableAccelNwForCtlrVmSwitch\": {\n            \"defaultValue\": false,\n            \"metadata\": {\n                \"description\": \"Switch to enable Azure Accelerated Networking on the controller VM. Default to false because currently the default controller VM SKU (D1) doesn't support AN. Change this to true if you set the controller VM SKU to eligibible ones (e.g., D2) for better performance.\"\n            },\n            \"type\": \"bool\"\n        },\n        \"enableAccelNwForOtherVmsSwitch\": {\n            \"defaultValue\": true,\n            \"metadata\": {\n                \"description\": \"Switch to enable Azure Accelerated Networking on all other VMs. Default to true because currently the default controller VM SKU for all other VMS (D2) does support AN. Change this to false if you set the SKU of any other VMs to an ineligibible one (e.g., D1) to avoid deployment failure.\"\n            },\n            \"type\": \"bool\"\n        },\n        \"vnetDbDeploySwitch\": {\n            \"defaultValue\": false,\n            \"metadata\": {\n                \"description\": \"Switch to deploying the db in VNet. Note: currently the Vnet DB Deployment is available for mysqlflex only. For other db server types, the database will not be deployed in VNet.\"\n            },\n            \"type\": \"bool\"\n        },\n        \"httpsTermination\": {\n            \"allowedValues\": [\n                \"VMSS\",\n                \"AppGw\",\n                \"None\"\n            ],\n            \"defaultValue\": \"VMSS\",\n            \"metadata\": {\n                \"description\": \"Indicates where https termination occurs. 'VMSS' is for https termination at the VMSS instance VMs (using nginx https proxy). 'AppGw' is for https termination with an Azure Application Gateway. When selecting this, you need to specify all appGw* parameters. 'None' is for testing only with no https. 'None' may not be used with a separately configured https termination layer. If you want to use the 'None' option with your separately configured https termination layer, you'll need to update your Moodle config.php manually for $cfg->wwwroot and $cfg->sslproxy.\"\n            },\n            \"type\": \"string\"\n        },\n        \"siteURL\": {\n            \"defaultValue\": \"www.example.org\",\n            \"metadata\": {\n                \"description\": \"URL for Moodle site\"\n            },\n            \"type\": \"string\"\n        },\n        \"moodleVersion\": {\n            \"allowedValues\": [\n                \"MOODLE_405_STABLE\"\n            ],\n            \"defaultValue\": \"MOODLE_405_STABLE\",\n            \"metadata\": {\n                \"description\": \"The Moodle version you want to install.\"\n            },\n            \"type\": \"string\"\n        },\n        \"sshPublicKey\": {\n            \"metadata\": {\n                \"description\": \"ssh public key\"\n            },\n            \"type\": \"string\"\n        },\n        \"sshUsername\": {\n            \"defaultValue\": \"azureadmin\",\n            \"metadata\": {\n                \"description\": \"ssh user name\"\n            },\n            \"type\": \"string\"\n        },\n        \"controllerVmSku\": {\n            \"defaultValue\": \"Standard_DS1_v2\",\n            \"metadata\": {\n                \"description\": \"VM size for the controller VM\"\n            },\n            \"type\": \"string\"\n        },\n        \"webServerType\": {\n            \"defaultValue\": \"nginx\",\n            \"allowedValues\": [\n                \"apache\",\n                \"nginx\"\n            ],\n            \"metadata\": {\n                \"description\": \"Web server type\"\n            },\n            \"type\": \"string\"\n        },\n        \"autoscaleVmSku\": {\n            \"defaultValue\": \"Standard_DS2_v2\",\n            \"metadata\": {\n                \"description\": \"VM size for autoscaled web VMs\"\n            },\n            \"type\": \"string\"\n        },\n        \"autoscaleVmCountMax\": {\n            \"defaultValue\": 10,\n            \"metadata\": {\n                \"description\": \"Maximum number of autoscaled web VMs\"\n            },\n            \"type\": \"int\"\n        },\n        \"autoscaleVmCountMin\": {\n            \"defaultValue\": 1,\n            \"metadata\": {\n                \"description\": \"Minimum (also initial) number of autoscaled web VMs\"\n            },\n            \"type\": \"int\"\n        },\n        \"osDiskStorageType\": {\n            \"defaultValue\": \"Premium_LRS\",\n            \"allowedValues\": [\n                \"Premium_LRS\",\n                \"Standard_LRS\"\n            ],\n            \"metadata\": {\n                \"description\": \"Azure storage type for all VMs' OS disks. With htmlLocalCopySwith true, Premium_LRS (SSD) is strongly recommended, as PHP files will be served from OS disks.\"\n            },\n            \"type\": \"string\"\n        },\n        \"phpVersion\": {\n            \"allowedValues\": [\n                \"8.1\"\n            ],\n            \"defaultValue\": \"8.1\",\n            \"metadata\": {\n                \"description\": \"php version\"\n            },\n            \"type\": \"string\"\n        },\n        \"dbServerType\": {\n            \"defaultValue\": \"mysqlflex\",\n            \"allowedValues\": [\n                \"postgres\",\n                \"mysql\",\n                \"mssql\",\n                \"mysqlflex\"\n            ],\n            \"metadata\": {\n                \"description\": \"Database type\"\n            },\n            \"type\": \"string\"\n        },\n        \"dbLogin\": {\n            \"defaultValue\": \"dbadmin\",\n            \"metadata\": {\n                \"description\": \"Database admin username\"\n            },\n            \"type\": \"string\"\n        },\n        \"moodleDbName\": {\n            \"defaultValue\": \"moodle\",\n            \"metadata\": {\n                \"description\": \"Moodle Database name\"\n            },\n            \"type\": \"string\"\n        },\n        \"moodleDbUser\": {\n            \"defaultValue\": \"moodle\",\n            \"metadata\": {\n                \"description\": \"Moodle Database username. This user is different from Database admin user.\"\n            },\n            \"type\": \"string\"\n        },\n        \"mysqlPgresVcores\": {\n            \"allowedValues\": [\n                1,\n                2,\n                4,\n                8,\n                16,\n                32\n            ],\n            \"defaultValue\": 2,\n            \"metadata\": {\n                \"description\": \"MySql/Postgresql vCores. For Basic tier, only 1 & 2 are allowed. For GeneralPurpose tier, 2, 4, 8, 16, 32 are allowed. For MemoryOptimized, 2, 4, 8, 16 are allowed.\"\n            },\n            \"type\": \"int\"\n        },\n        \"mysqlPgresStgSizeGB\": {\n            \"defaultValue\": 125,\n            \"minValue\": 5,\n            \"maxValue\": 1024,\n            \"metadata\": {\n                \"description\": \"MySql/Postgresql storage size in GB. Minimum 5GB, increase by 1GB, up to 1TB (1024 GB)\"\n            },\n            \"type\": \"int\"\n        },\n        \"mysqlPgresSkuTier\": {\n            \"allowedValues\": [\n                \"Basic\",\n                \"GeneralPurpose\",\n                \"MemoryOptimized\"\n            ],\n            \"defaultValue\": \"GeneralPurpose\",\n            \"metadata\": {\n                \"description\": \"MySql/Postgresql sku tier\"\n            },\n            \"type\": \"string\"\n        },\n        \"mysqlPgresSkuHwFamily\": {\n            \"allowedValues\": [\n                \"Gen4\",\n                \"Gen5\"\n            ],\n            \"defaultValue\": \"Gen5\",\n            \"metadata\": {\n                \"description\": \"MySql/Postgresql sku hardware family. Central US is Gen4 only, so make sure to change this parameter to Gen4 if your deployment is on Central US.\"\n            },\n            \"type\": \"string\"\n        },\n        \"mysqlVersion\": {\n            \"allowedValues\": [\n                \"8.0.21\"\n            ],\n            \"defaultValue\": \"8.0.21\",\n            \"metadata\": {\n                \"description\": \"Mysql version\"\n            },\n            \"type\": \"string\"\n        },\n        \"mysqlflexSkuName\": {\n            \"allowedValues\": [\n                \"Standard_B1s\",\n                \"Standard_B1ms\",\n                \"Standard_B2s\",\n                \"Standard_B2ms\",\n                \"Standard_B4ms\",\n                \"Standard_B8ms\",\n                \"Standard_B12ms\",\n                \"Standard_B16ms\",\n                \"Standard_B20ms\",\n                \"Standard_D2ads_v5\",\n                \"Standard_D2ds_v4\",\n                \"Standard_D4ads_v5\",\n                \"Standard_D4ds_v4\",\n                \"Standard_D8ads_v5\",\n                \"Standard_D8ds_v4\",\n                \"Standard_D16ads_v5\",\n                \"Standard_D16ds_v4\",\n                \"Standard_D32ads_v5\",\n                \"Standard_D32ds_v4\",\n                \"Standard_D48ads_v5\",\n                \"Standard_D48ds_v4\",\n                \"Standard_D64ads_v5\",\n                \"Standard_D64ds_v4\",\n                \"Standard_E2ds_v4\",\n                \"Standard_E2ads_v5\",\n                \"Standard_E4ds_v4\",\n                \"Standard_E4ads_v5\",\n                \"Standard_E8ds_v4\",\n                \"Standard_E8ads_v5\",\n                \"Standard_E16ds_v4\",\n                \"Standard_E16ads_v5\",\n                \"Standard_E32ds_v4\",\n                \"Standard_E32ads_v5\",\n                \"Standard_E48ds_v4\",\n                \"Standard_E48ads_v5\",\n                \"Standard_E64ds_v4\",\n                \"Standard_E64ads_v5\",\n                \"Standard_E80ids_v4\",\n                \"Standard_E2ds_v5\",\n                \"Standard_E4ds_v5\",\n                \"Standard_E8ds_v5\",\n                \"Standard_E16ds_v5\",\n                \"Standard_E32ds_v5\",\n                \"Standard_E48ds_v5\",\n                \"Standard_E64ds_v5\",\n                \"Standard_E96ds_v5\"\n            ],\n            \"defaultValue\": \"Standard_B2s\",\n            \"metadata\": {\n                \"description\": \"The name of the sku for Azure Database for MySQL Flexible Servers, e.g. Standard_D32ds_v4. Complete list is available here : https://learn.microsoft.com/en-us/azure/mysql/flexible-server/concepts-service-tiers-storage \"\n            },\n            \"type\": \"string\"\n        },\n        \"mysqlflexStgSizeGiB\": {\n            \"defaultValue\": 20,\n            \"minValue\": 20,\n            \"maxValue\": 16384,\n            \"metadata\": {\n                \"description\": \"Azure Database for MySQL Flexible server storage size in GiB. Minimum 20GiB, increase by 1GiB, up to 16TiB (1024 GiB)\"\n            },\n            \"type\": \"int\"\n        },\n        \"mysqlflexStgIops\": {\n            \"defaultValue\": 360,\n            \"minValue\": 360,\n            \"metadata\": {\n                \"description\": \"Azure Database for MySQL Flexible server storage iops. Minimum 360, Maxium is determined by selected mysqlflexSkuName.\"\n            },\n            \"type\": \"int\"\n        },\n        \"mysqlflexStgAutogrow\": {\n            \"allowedValues\": [\n                \"Enabled\",\n                \"Disabled\"\n            ],\n            \"defaultValue\": \"Enabled\",\n            \"type\": \"string\"\n        },\n        \"mysqlflexHaEnabled\": {\n            \"allowedValues\": [\n                \"Disabled\",\n                \"SameZone\",\n                \"ZoneRedundant\"\n            ],\n            \"defaultValue\": \"Disabled\",\n            \"metadata\": {\n              \"description\": \"High availability mode for azure database for mysql flexible server.\"\n            },\n            \"type\": \"string\"\n        },\n        \"mysqlflexAvailabilityZone\": {\n            \"allowedValues\": [\n                \"1\",\n                \"2\",\n                \"3\",\n                \"\"\n            ],\n            \"defaultValue\": \"\",\n            \"metadata\": {\n                \"description\": \"Availability Zone information of the server. (Leave blank for No Preference).\"\n            },\n            \"type\": \"string\"\n        },\n        \"mysqlflexStandbyAvailabilityZone\": {\n            \"allowedValues\": [\n                \"1\",\n                \"2\",\n                \"3\",\n                \"\"\n            ],\n            \"defaultValue\": \"\",\n            \"metadata\": {\n            \"description\": \"Availability zone of the High availability standby server. (Leave blank for No Preference). Add this value if HA is enabled.\"\n            },\n            \"type\": \"string\"\n        },\n        \"postgresVersion\": {\n            \"allowedValues\": [\n                \"9.6\"\n            ],\n            \"defaultValue\": \"9.6\",\n            \"metadata\": {\n                \"description\": \"Postgresql version\"\n            },\n            \"type\": \"string\"\n        },\n        \"sslEnforcement\": {\n            \"allowedValues\": [\n                \"Disabled\",\n                \"Enabled\"\n            ],\n            \"defaultValue\": \"Disabled\",\n            \"metadata\": {\n                \"description\": \"MySql/Postgresql SSL connection\"\n            },\n            \"type\": \"string\"\n        },\n        \"mssqlDbServiceObjectiveName\": {\n            \"allowedValues\": [\n                \"S1\",\n                \"S2\",\n                \"S3\",\n                \"S4\",\n                \"S5\",\n                \"S6\",\n                \"S7\",\n                \"S9\"\n            ],\n            \"defaultValue\": \"S1\",\n            \"metadata\": {\n                \"description\": \"MS SQL database service object names\"\n            },\n            \"type\": \"string\"\n        },\n        \"mssqlDbSize\": {\n            \"allowedValues\": [\n                \"100MB\",\n                \"250MB\",\n                \"500MB\",\n                \"1GB\",\n                \"2GB\",\n                \"5GB\",\n                \"10GB\",\n                \"20GB\",\n                \"30GB\",\n                \"40GB\",\n                \"50GB\",\n                \"100GB\",\n                \"250GB\",\n                \"300GB\",\n                \"400GB\",\n                \"500GB\",\n                \"750GB\",\n                \"1024GB\"\n            ],\n            \"defaultValue\": \"250GB\",\n            \"metadata\": {\n                \"description\": \"MS SQL database size\"\n            },\n            \"type\": \"string\"\n        },\n        \"mssqlDbEdition\": {\n            \"allowedValues\": [\n                \"Basic\",\n                \"Standard\"\n            ],\n            \"defaultValue\": \"Standard\",\n            \"metadata\": {\n                \"description\": \"MS SQL DB edition\"\n            },\n            \"type\": \"string\"\n        },\n        \"mssqlVersion\": {\n            \"allowedValues\": [\n                \"12.0\"\n            ],\n            \"defaultValue\": \"12.0\",\n            \"metadata\": {\n                \"description\": \"Mssql version\"\n            },\n            \"type\": \"string\"\n        },\n        \"fileServerType\": {\n            \"defaultValue\": \"nfs\",\n            \"allowedValues\": [\n                \"gluster\",\n                \"nfs\",\n                \"nfs-ha\",\n                \"nfs-byo\",\n                \"azurefiles\"\n            ],\n            \"metadata\": {\n                \"description\": \"File server type: GlusterFS, NFS, and NFS-HA (2-VM highly available NFS cluster)\"\n            },\n            \"type\": \"string\"\n        },\n        \"nfsByoIpExportPath\": {\n            \"defaultValue\": \"\",\n            \"metadata\": {\n                \"description\": \"IP address and export path of the BYO-NFS share when fileServerType == nfs-byo. E.g., 172.16.1.8:/msazure\"\n            },\n            \"type\": \"string\"\n        },\n        \"OSDiskSizeInGB\": {\n            \"defaultValue\": 256,\n            \"metadata\": {\n                \"description\": \"OS disk size per Webserver in VMSS\"\n            },\n            \"type\": \"int\"\n        },\n        \"fileServerDiskSize\": {\n            \"defaultValue\": 127,\n            \"metadata\": {\n                \"description\": \"Size per disk for gluster nodes or nfs server\"\n            },\n            \"type\": \"int\"\n        },\n        \"fileServerDiskCount\": {\n            \"defaultValue\": 4,\n            \"minValue\": 2,\n            \"maxValue\": 8,\n            \"metadata\": {\n                \"description\": \"Number of disks in raid0 per gluster node or nfs server\"\n            },\n            \"type\": \"int\"\n        },\n        \"fileServerVmSku\": {\n            \"defaultValue\": \"Standard_DS2_v2\",\n            \"metadata\": {\n                \"description\": \"VM size for the gluster or NFS-HA nodes\"\n            },\n            \"type\": \"string\"\n        },\n        \"keyVaultResourceId\": {\n            \"defaultValue\": \"\",\n            \"metadata\": {\n                \"description\": \"(VMSS https termination only) Azure Resource Manager resource ID of the Key Vault in case you stored your SSL cert in an Azure Key Vault (Note that this Key Vault must have been pre-created on the same Azure region where this template is being deployed). Leave this blank if you didn't. Resource ID example: /subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/xxx/providers/Microsoft.KeyVault/vaults/yyy. This value can be obtained from keyvault.sh output if you used the script to store your SSL cert in your Key Vault.\"\n            },\n            \"type\": \"string\"\n        },\n        \"sslCertKeyVaultURL\": {\n            \"defaultValue\": \"\",\n            \"metadata\": {\n                \"description\": \"(VMSS https termination only) Azure Key Vault URL for your stored SSL cert. This value can be obtained from keyvault.sh output if you used the script to store your SSL cert in your Key Vault. This parameter is ignored if the keyVaultResourceId parameter is blank.\"\n            },\n            \"type\": \"string\"\n        },\n        \"sslCertThumbprint\": {\n            \"defaultValue\": \"\",\n            \"metadata\": {\n                \"description\": \"(VMSS https termination only) Thumbprint of your stored SSL cert. This value can be obtained from keyvault.sh output if you used the script to store your SSL cert in your Key Vault. This parameter is ignored if the keyVaultResourceId parameter is blank.\"\n            },\n            \"type\": \"string\"\n        },\n        \"caCertKeyVaultURL\": {\n            \"defaultValue\": \"\",\n            \"metadata\": {\n                \"description\": \"(VMSS https termination only) Azure Key Vault URL for your stored CA (Certificate Authority) cert. This value can be obtained from keyvault.sh output if you used the script to store your CA cert in your Key Vault. This parameter is ignored if the keyVaultResourceId parameter is blank.\"\n            },\n            \"type\": \"string\"\n        },\n        \"caCertThumbprint\": {\n            \"defaultValue\": \"\",\n            \"metadata\": {\n                \"description\": \"(VMSS https termination only) Thumbprint of your stored CA cert. This value can be obtained from keyvault.sh output if you used the script to store your CA cert in your Key Vault. This parameter is ignored if the keyVaultResourceId parameter is blank.\"\n            },\n            \"type\": \"string\"\n        },\n        \"appGwSslCertKeyVaultResourceId\": {\n            \"defaultValue\": \"\",\n            \"metadata\": {\n                \"description\": \"(App Gateway https termination only) Azure Key Vault URL for your stored SSL cert, again for App Gateway https termination case only. (Note that this Key Vault must have been pre-created on the same Azure region where this template is being deployed). Leave this blank if you didn't. Resource ID example: /subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/xxx/providers/Microsoft.KeyVault/vaults/yyy.\"\n            },\n            \"type\": \"string\"\n        },\n        \"appGwSslCertKeyVaultSecretName\": {\n            \"defaultValue\": \"\",\n            \"metadata\": {\n                \"description\": \"(App Gateway https termination only) Name of the Azure Key Vault secret that's stored in the previously specified Key Vault as a PFX certificate (with no password) for your site's SSL cert. This secret must be pre-populated in the specified Key Vault with the matching name.\"\n            },\n            \"type\": \"string\"\n        },\n        \"appGwSkuName\": {\n            \"defaultValue\": \"Standard_v2\",\n            \"allowedValues\": [\n                \"Standard_Small\",\n                \"Standard_Medium\",\n                \"Standard_Large\",\n                \"Standard_v2\",\n                \"WAF_Medium\",\n                \"WAF_Large\",\n                \"WAF_v2\"\n            ],\n            \"metadata\": {\n                \"description\": \"(App Gateway https termination only) Name of the Applicate Gateway SKU\"\n            },\n            \"type\": \"string\"\n        },\n        \"appGwSkuTier\": {\n            \"defaultValue\": \"Standard_v2\",\n            \"allowedValues\": [\n                \"Standard\",\n                \"Standard_v2\",\n                \"WAF\",\n                \"WAF_v2\"\n            ],\n            \"metadata\": {\n                \"description\": \"(App Gateway https termination only) Tier of the Applicate Gateway\"\n            },\n            \"type\": \"string\"\n        },\n        \"appGwSkuCapacity\": {\n            \"defaultValue\": 2,\n            \"maxValue\": 10,\n            \"minValue\": 2,\n            \"metadata\": {\n                \"description\": \"(App Gateway https termination only) Capacity instance count) of the Applicate Gateway\"\n            },\n            \"type\": \"int\"\n        },\n        \"storageAccountType\": {\n            \"defaultValue\": \"Standard_LRS\",\n            \"allowedValues\": [\n                \"Standard_LRS\",\n                \"Standard_GRS\",\n                \"Standard_ZRS\",\n                \"Premium_LRS\"\n            ],\n            \"metadata\": {\n                \"description\": \"Storage Account type. This storage account is only for the Moodle ObjectFS plugin and/or the (currently disabled) Azure Files file share option\"\n            },\n            \"type\": \"string\"\n        },\n        \"searchType\": {\n            \"defaultValue\": \"none\",\n            \"allowedValues\": [\n                \"none\",\n                \"azure\",\n                \"elastic\"\n            ],\n            \"metadata\": {\n                \"description\": \"options of moodle global search\"\n            },\n            \"type\": \"string\"\n        },\n        \"tikaService\": {\n            \"defaultValue\": \"none\",\n            \"allowedValues\": [\n                \"none\",\n                \"tika\"\n            ],\n            \"metadata\": {\n                \"description\": \"options of enabling tika service for file searching in moodle\"\n            },\n            \"type\": \"string\"\n        },\n        \"azureSearchSku\": {\n            \"defaultValue\": \"basic\",\n            \"allowedValues\": [\n                \"free\",\n                \"basic\",\n                \"standard\",\n                \"standard2\",\n                \"standard3\"\n            ],\n            \"metadata\": {\n                \"description\": \"the search service level you want to create.\"\n            },\n            \"type\": \"string\"\n        },\n        \"azureSearchReplicaCount\": {\n            \"defaultValue\": 3,\n            \"minValue\": 1,\n            \"maxValue\": 12,\n            \"metadata\": {\n                \"description\": \"Replicas distribute search workloads across the service. You need 2 or more to support high availability (applies to Basic and Standard only).\"\n            },\n            \"type\": \"int\"\n        },\n        \"azureSearchPartitionCount\": {\n            \"defaultValue\": 1,\n            \"allowedValues\": [\n                1,\n                2,\n                3,\n                4,\n                6,\n                12\n            ],\n            \"metadata\": {\n                \"description\": \"Partitions allow for scaling of document count as well as faster indexing by sharding your index over multiple Azure Search units.\"\n            },\n            \"type\": \"int\"\n        },\n        \"azureSearchHostingMode\": {\n            \"defaultValue\": \"default\",\n            \"allowedValues\": [\n                \"default\",\n                \"highDensity\"\n            ],\n            \"metadata\": {\n                \"description\": \"Applicable only for azureSearchSku set to standard3. You can set this property to enable a single, high density partition that allows up to 1000 indexes, which is much higher than the maximum indexes allowed for any other azureSearchSku.\"\n            },\n            \"type\": \"string\"\n        },\n        \"elasticVmSku\": {\n            \"defaultValue\": \"Standard_DS2_v2\",\n            \"metadata\": {\n                \"description\": \"VM size for the elastic search nodes\"\n            },\n            \"type\": \"string\"\n        },\n        \"tikaVmSku\": {\n            \"defaultValue\": \"Standard_DS2_v2\",\n            \"metadata\": {\n                \"description\": \"VM size for the tika search nodes\"\n            },\n            \"type\": \"string\"\n        },\n        \"customVnetId\": {\n            \"defaultValue\": \"\",\n            \"metadata\": {\n                \"description\": \"Azure Resource ID of the Azure virtual network where you want to deploy your Moodle resources. A vnet resource ID is of the following format: /subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxxxxxx/resourceGroups/gggg/providers/Microsoft.Network/virtualNetworks/vvvv. Note that this virtual network must be on the same Azure location as this template deployment location. If this parameter is blank, a new Azure virtual network will be created and used. In that case, the address space of the newly created virtual network will be */16 of the following vNetAddressSpace parameter value below.\"\n            },\n            \"type\": \"string\"\n        },\n        \"vNetAddressSpace\": {\n            \"defaultValue\": \"172.31.0.0\",\n            \"metadata\": {\n                \"description\": \"Address range for the Moodle virtual network and various subnets - presumed /16 for a newly created vnet in case customVnetId is blank. Further subneting (a number of */24 subnets starting from the xxx.yyy.zzz.0/24 will be created on a newly created vnet or your BYO-vnet (specified in customVnetId parameter).\"\n            },\n            \"type\": \"string\"\n        },\n        \"gatewayType\": {\n            \"allowedValues\": [\n                \"Vpn\",\n                \"ER\"\n            ],\n            \"defaultValue\": \"Vpn\",\n            \"metadata\": {\n                \"description\": \"Virtual network gateway type\"\n            },\n            \"type\": \"string\"\n        },\n        \"vpnType\": {\n            \"allowedValues\": [\n                \"RouteBased\",\n                \"PolicyBased\"\n            ],\n            \"defaultValue\": \"RouteBased\",\n            \"metadata\": {\n                \"description\": \"Virtual network gateway vpn type\"\n            },\n            \"type\": \"string\"\n        },\n        \"loadBalancerSku\": {\n            \"defaultValue\": \"Standard\",\n            \"allowedValues\": [\n                \"Basic\",\n                \"Standard\"\n            ],\n            \"metadata\": {\n                \"description\": \"Loadbalancer SKU\"\n            },\n            \"type\": \"string\"\n        },\n        \"loadBalancerTier\": {\n            \"defaultValue\": \"Regional\",\n            \"allowedValues\": [\n                \"Regional\",\n                \"Global\"\n            ],\n            \"metadata\": {\n                \"description\": \"Loadbalancer Tier\"\n            },\n            \"type\": \"string\"\n        },\n        \"ubuntuVersion\": {\n            \"type\": \"string\",\n            \"allowedValues\": [\n                \"22_04-lts-gen2\"\n            ],\n            \"defaultValue\": \"22_04-lts-gen2\"\n        },\n        \"location\": {\n            \"type\": \"string\",\n            \"defaultValue\": \"[resourceGroup().location]\",\n            \"metadata\": {\n                \"description\": \"Azure Location for all resources.\"\n            }\n        },\n        \"isMigration\": {\n            \"type\": \"bool\",\n            \"defaultValue\": false,\n            \"metadata\": {\n                \"description\": \"Indicates whether the this template is using for migration scenario.\"\n            }\n        }\n    },\n    \"resources\": [\n        {\n            \"type\": \"Microsoft.Resources/deployments\",\n            \"apiVersion\": \"2017-05-10\",\n            \"name\": \"pid-738e3eec-68d4-4667-8377-c05c77c21f1b\",\n            \"properties\": {\n                \"mode\": \"Incremental\",\n                \"template\": {\n                    \"$schema\": \"https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#\",\n                    \"contentVersion\": \"1.0.0.0\",\n                    \"resources\": []\n                }\n            }\n        },\n        {\n            \"type\": \"Microsoft.Resources/deployments\",\n            \"apiVersion\": \"2017-05-10\",\n            \"name\": \"networkTemplate\",\n            \"properties\": {\n                \"mode\": \"Incremental\",\n                \"parameters\": {\n                    \"moodleCommon\": {\n                        \"value\": \"[variables('moodleCommon')]\"\n                    }\n                },\n                \"templateLink\": {\n                    \"uri\": \"[concat(variables('moodleCommon').baseTemplateUrl,'network.json',parameters('_artifactsLocationSasToken'))]\"\n                }\n            }\n        },\n        {\n            \"type\": \"Microsoft.Resources/deployments\",\n            \"apiVersion\": \"2017-05-10\",\n            \"name\": \"storageAccountTemplate\",\n            \"properties\": {\n                \"mode\": \"Incremental\",\n                \"parameters\": {\n                    \"moodleCommon\": {\n                        \"value\": \"[variables('moodleCommon')]\"\n                    }\n                },\n                \"templateLink\": {\n                    \"uri\": \"[concat(variables('moodleCommon').baseTemplateUrl,'storageAccount.json',parameters('_artifactsLocationSasToken'))]\"\n                }\n            }\n        },\n        {\n            \"type\": \"Microsoft.Resources/deployments\",\n            \"apiVersion\": \"2017-05-10\",\n            \"dependsOn\": [\n                \"Microsoft.Resources/deployments/networkTemplate\"\n            ],\n            \"name\": \"dbTemplate\",\n            \"properties\": {\n                \"mode\": \"Incremental\",\n                \"parameters\": {\n                    \"moodleCommon\": {\n                        \"value\": \"[variables('moodleCommon')]\"\n                    },\n                    \"lbPubIp\": {\n                        \"value\": \"[reference('networkTemplate').outputs.lbPubIp.value]\"\n                    },\n                    \"lbOut001PubIp\": {\n                        \"value\": \"[reference('networkTemplate').outputs.lbOut001PubIp.value]\"\n                    },\n                    \"lbOut002PubIp\": {\n                        \"value\": \"[reference('networkTemplate').outputs.lbOut002PubIp.value]\"\n                    },\n                    \"ctlrPubIp\": {\n                        \"value\": \"[reference('networkTemplate').outputs.ctlrPubIp.value]\"\n                    },\n                    \"subnetIdDb\": {\n                        \"value\": \"[reference('networkTemplate').outputs.subnetIdDb.value]\"\n                    }\n                },\n                \"templateLink\": {\n                    \"uri\": \"[concat(variables('moodleCommon').baseTemplateUrl, 'db-', parameters('dbServerType'), '.json', parameters('_artifactsLocationSasToken'))]\"\n                }\n            }\n        },\n        {\n            \"condition\": \"[parameters('azureBackupSwitch')]\",\n            \"type\": \"Microsoft.Resources/deployments\",\n            \"apiVersion\": \"2017-05-10\",\n            \"name\": \"recoveryTemplate\",\n            \"properties\": {\n                \"mode\": \"Incremental\",\n                \"parameters\": {\n                    \"moodleCommon\": {\n                        \"value\": \"[variables('moodleCommon')]\"\n                    }\n                },\n                \"templateLink\": {\n                    \"uri\": \"[concat(variables('moodleCommon').baseTemplateUrl,'recoveryservices.json',parameters('_artifactsLocationSasToken'))]\"\n                }\n            }\n        },\n        {\n            \"condition\": \"[parameters('redisDeploySwitch')]\",\n            \"type\": \"Microsoft.Resources/deployments\",\n            \"apiVersion\": \"2017-05-10\",\n            \"name\": \"redisTemplate\",\n            \"dependsOn\": [\n                \"Microsoft.Resources/deployments/networkTemplate\"\n            ],\n            \"properties\": {\n                \"mode\": \"Incremental\",\n                \"parameters\": {\n                    \"moodleCommon\": {\n                        \"value\": \"[variables('moodleCommon')]\"\n                    },\n                    \"subnetIdRedis\": {\n                        \"value\": \"[reference('networkTemplate').outputs.subnetIdRedis.value]\"\n                    }\n                },\n                \"templateLink\": {\n                    \"uri\": \"[concat(variables('moodleCommon').baseTemplateUrl, 'redis.json', parameters('_artifactsLocationSasToken'))]\"\n                }\n            }\n        },\n        {\n            \"condition\": \"[not(equals(parameters('searchType'), 'none'))]\",\n            \"type\": \"Microsoft.Resources/deployments\",\n            \"apiVersion\": \"2017-05-10\",\n            \"dependsOn\": [\n                \"Microsoft.Resources/deployments/networkTemplate\",\n                \"Microsoft.Resources/deployments/recoveryTemplate\"\n            ],\n            \"name\": \"searchTemplate\",\n            \"properties\": {\n                \"mode\": \"Incremental\",\n                \"parameters\": {\n                    \"moodleCommon\": {\n                        \"value\": \"[variables('moodleCommon')]\"\n                    },\n                    \"subnetIdElastic\": {\n                        \"value\": \"[reference('networkTemplate').outputs.subnetIdElastic.value]\"\n                    }\n                },\n                \"templateLink\": {\n                    \"uri\": \"[concat(variables('moodleCommon').baseTemplateUrl, 'search-', parameters('searchType'), '.json', parameters('_artifactsLocationSasToken'))]\"\n                }\n            }\n        },\n        {\n            \"condition\": \"[and(equals(parameters('tikaService'), 'tika'), not(equals(parameters('searchType'), 'none')))]\",\n            \"type\": \"Microsoft.Resources/deployments\",\n            \"apiVersion\": \"2017-05-10\",\n            \"dependsOn\": [\n                \"Microsoft.Resources/deployments/networkTemplate\",\n                \"Microsoft.Resources/deployments/recoveryTemplate\"\n            ],\n            \"name\": \"tikaTemplate\",\n            \"properties\": {\n                \"mode\": \"Incremental\",\n                \"parameters\": {\n                    \"moodleCommon\": {\n                        \"value\": \"[variables('moodleCommon')]\"\n                    },\n                    \"subnetIdTika\": {\n                        \"value\": \"[reference('networkTemplate').outputs.subnetIdTika.value]\"\n                    }\n                },\n                \"templateLink\": {\n                    \"uri\": \"[concat(variables('moodleCommon').baseTemplateUrl, 'tika.json', parameters('_artifactsLocationSasToken'))]\"\n                }\n            }\n        },\n        {\n            \"condition\": \"[equals(parameters('fileServerType'),'gluster')]\",\n            \"type\": \"Microsoft.Resources/deployments\",\n            \"apiVersion\": \"2017-05-10\",\n            \"dependsOn\": [\n                \"Microsoft.Resources/deployments/networkTemplate\",\n                \"Microsoft.Resources/deployments/recoveryTemplate\"\n            ],\n            \"name\": \"glusterTemplate\",\n            \"properties\": {\n                \"mode\": \"Incremental\",\n                \"parameters\": {\n                    \"moodleCommon\": {\n                        \"value\": \"[variables('moodleCommon')]\"\n                    },\n                    \"subnetIdSan\": {\n                        \"value\": \"[reference('networkTemplate').outputs.subnetIdSan.value]\"\n                    }\n                },\n                \"templateLink\": {\n                    \"uri\": \"[concat(variables('moodleCommon').baseTemplateUrl,'gluster.json',parameters('_artifactsLocationSasToken'))]\"\n                }\n            }\n        },\n        {\n            \"condition\": \"[equals(parameters('fileServerType'),'nfs-ha')]\",\n            \"type\": \"Microsoft.Resources/deployments\",\n            \"apiVersion\": \"2017-05-10\",\n            \"dependsOn\": [\n                \"Microsoft.Resources/deployments/networkTemplate\",\n                \"Microsoft.Resources/deployments/recoveryTemplate\"\n            ],\n            \"name\": \"nfsHaTemplate\",\n            \"properties\": {\n                \"mode\": \"Incremental\",\n                \"parameters\": {\n                    \"_artifactsLocation\": {\n                        \"value\": \"[parameters('_artifactsLocation')]\"\n                    },\n                    \"_artifactsLocationSasToken\": {\n                        \"value\": \"[parameters('_artifactsLocationSasToken')]\"\n                    },\n                    \"location\": {\n                        \"value\": \"[parameters('location')]\"\n                    },\n                    \"subnetId\": {\n                        \"value\": \"[reference('networkTemplate').outputs.subnetIdSan.value]\"\n                    },\n                    \"node0IPAddr\": {\n                        \"value\": \"[variables('moodleCommon').nfsHaNode0IP]\"\n                    },\n                    \"node1IPAddr\": {\n                        \"value\": \"[variables('moodleCommon').nfsHaNode1IP]\"\n                    },\n                    \"nfsClientsIPRange\": {\n                        \"value\": \"[variables('moodleCommon').nfsHaClientsIPRange]\"\n                    },\n                    \"lbFrontEndIpAddr\": {\n                        \"value\": \"[variables('moodleCommon').nfsHaLbIP]\"\n                    },\n                    \"enableAccelNwSwitch\": {\n                        \"value\": \"[parameters('enableAccelNwForOtherVmsSwitch')]\"\n                    },\n                    \"vmSku\": {\n                        \"value\": \"[variables('moodleCommon').fileServerVmSku]\"\n                    },\n                    \"adminUserName\": {\n                        \"value\": \"[parameters('sshUsername')]\"\n                    },\n                    \"sshPublicKey\": {\n                        \"value\": \"[parameters('sshPublicKey')]\"\n                    },\n                    \"osType\": {\n                        \"value\": \"[variables('moodleCommon').osType]\"\n                    },\n                    \"osDiskStorageType\": {\n                        \"value\": \"[parameters('osDiskStorageType')]\"\n                    },\n                    \"dataDiskCountPerVM\": {\n                        \"value\": \"[parameters('fileServerDiskCount')]\"\n                    },\n                    \"dataDiskSizeInGB\": {\n                        \"value\": \"[parameters('fileServerDiskSize')]\"\n                    },\n                    \"OSDiskSizeInGB\": {\n                        \"value\": \"[parameters('OSDiskSizeInGB')]\"\n                    },\n                    \"resourcesUniqueString\": {\n                        \"value\": \"[variables('resourceprefix')]\"\n                    }\n                },\n                \"templateLink\": {\n                    \"uri\": \"[concat(variables('moodleCommon').baseTemplateUrl, 'nfs-ha.json', parameters('_artifactsLocationSasToken'))]\"\n                }\n            }\n        },\n        {\n            \"type\": \"Microsoft.Resources/deployments\",\n            \"apiVersion\": \"2017-05-10\",\n            \"dependsOn\": [\n                \"Microsoft.Resources/deployments/networkTemplate\",\n                \"Microsoft.Resources/deployments/dbTemplate\",\n                \"Microsoft.Resources/deployments/redisTemplate\",\n                \"Microsoft.Resources/deployments/searchTemplate\",\n                \"Microsoft.Resources/deployments/storageAccountTemplate\"\n            ],\n            \"name\": \"vmSetupParamsTemplate\",\n            \"properties\": {\n                \"mode\": \"Incremental\",\n                \"parameters\": {\n                    \"moodleCommon\": {\n                        \"value\": \"[variables('moodleCommon')]\"\n                    },\n                    \"dbFQDN\": {\n                        \"value\": \"[reference('dbTemplate').outputs.dbFQDN.value]\"\n                    },\n                    \"storageAccountName\": {\n                        \"value\": \"[reference('storageAccountTemplate').outputs.storageAccountName.value]\"\n                    },\n                    \"storageAccountKey\": {\n                        \"value\": \"[reference('storageAccountTemplate').outputs.storageAccountKey.value]\"\n                    },\n                    \"redisKey\": {\n                        \"value\": \"[if(parameters('redisDeploySwitch'), reference('redisTemplate').outputs.redisKey.value, 'None')]\"\n                    },\n                    \"azureSearchKey\": {\n                        \"value\": \"[if(equals(parameters('searchType'), 'azure'), reference('searchTemplate').outputs.azureSearchKey.value, 'None')]\"\n                    }\n                },\n                \"templateLink\": {\n                    \"uri\": \"[concat(variables('moodleCommon').baseTemplateUrl,'vmsetupparams.json',parameters('_artifactsLocationSasToken'))]\"\n                }\n            }\n        },\n        {\n            \"type\": \"Microsoft.Resources/deployments\",\n            \"apiVersion\": \"2017-05-10\",\n            \"dependsOn\": [\n                \"Microsoft.Resources/deployments/vmSetupParamsTemplate\",\n                \"Microsoft.Resources/deployments/glusterTemplate\",\n                \"Microsoft.Resources/deployments/nfsHaTemplate\",\n                \"Microsoft.Resources/deployments/recoveryTemplate\",\n                \"Microsoft.Resources/deployments/networkTemplate\",\n                \"Microsoft.Resources/deployments/dbTemplate\",\n                \"Microsoft.Resources/deployments/redisTemplate\",\n                \"Microsoft.Resources/deployments/searchTemplate\",\n                \"Microsoft.Resources/deployments/storageAccountTemplate\"\n            ],\n            \"name\": \"controllerTemplate\",\n            \"properties\": {\n                \"mode\": \"Incremental\",\n                \"parameters\": {\n                    \"moodleCommon\": {\n                        \"value\": \"[variables('moodleCommon')]\"\n                    },\n                    \"subnetIdWeb\": {\n                        \"value\": \"[reference('networkTemplate').outputs.subnetIdWeb.value]\"\n                    },\n                    \"ctlrPubIpId\": {\n                        \"value\": \"[reference('networkTemplate').outputs.ctlrPubIpId.value]\"\n                    },\n                    \"vmSetupParamsObj\": {\n                        \"value\": \"[reference('vmSetupParamsTemplate').outputs.vmSetupParamsObj.value]\"\n                    }\n                },\n                \"templateLink\": {\n                    \"uri\": \"[concat(variables('moodleCommon').baseTemplateUrl,'controller.json',parameters('_artifactsLocationSasToken'))]\"\n                }\n            }\n        },\n        {\n            \"type\": \"Microsoft.Resources/deployments\",\n            \"apiVersion\": \"2017-05-10\",\n            \"dependsOn\": [\n                \"Microsoft.Resources/deployments/vmSetupParamsTemplate\",\n                \"Microsoft.Resources/deployments/controllerTemplate\",\n                \"Microsoft.Resources/deployments/networkTemplate\",\n                \"Microsoft.Resources/deployments/redisTemplate\",\n                \"Microsoft.Resources/deployments/dbTemplate\"\n            ],\n            \"name\": \"scaleSetTemplate\",\n            \"properties\": {\n                \"mode\": \"Incremental\",\n                \"parameters\": {\n                    \"moodleCommon\": {\n                        \"value\": \"[variables('moodleCommon')]\"\n                    },\n                    \"subnetIdWeb\": {\n                        \"value\": \"[reference('networkTemplate').outputs.subnetIdWeb.value]\"\n                    },\n                    \"vmSetupParamsObj\": {\n                        \"value\": \"[reference('vmSetupParamsTemplate').outputs.vmSetupParamsObj.value]\"\n                    }\n                },\n                \"templateLink\": {\n                    \"uri\": \"[concat(variables('moodleCommon').baseTemplateUrl,'webvmss.json',parameters('_artifactsLocationSasToken'))]\"\n                }\n            }\n        }\n    ],\n    \"outputs\": {\n        \"siteURL\": {\n            \"type\": \"string\",\n            \"value\": \"[variables('moodleCommon').siteURL]\"\n        },\n        \"controllerInstanceIP\": {\n            \"type\": \"string\",\n            \"value\": \"[reference('controllerTemplate').outputs.controllerIP.value]\"\n        },\n        \"databaseDNS\": {\n            \"type\": \"string\",\n            \"value\": \"[reference('dbTemplate').outputs.dbFQDN.value]\"\n        },\n        \"databaseAdminUsername\": {\n            \"type\": \"string\",\n            \"value\": \"[variables('moodleCommon').dbUsername]\"\n        },\n        \"databaseAdminPassword\": {\n            \"type\": \"string\",\n            \"value\": \"[variables('moodleCommon').dbLoginPassword]\"\n        },\n        \"firstFrontendVmIP\": {\n            \"type\": \"string\",\n            \"value\": \"[reference('scaleSetTemplate').outputs.webvm1IP.value]\"\n        },\n        \"moodleAdminPassword\": {\n            \"type\": \"string\",\n            \"value\": \"[variables('moodleCommon').moodleAdminPass]\"\n        },\n        \"moodleDbUsername\": {\n            \"type\": \"string\",\n            \"value\": \"[variables('moodleCommon').moodleDbUserAzure]\"\n        },\n        \"moodleDbPassword\": {\n            \"type\": \"string\",\n            \"value\": \"[variables('moodleCommon').moodleDbPass]\"\n        },\n        \"loadBalancerDNS\": {\n            \"type\": \"string\",\n            \"value\": \"[variables('moodleCommon').lbDns]\"\n        },\n        \"loadBalancerSku\": {\n            \"type\": \"string\",\n            \"value\": \"[variables('moodleCommon').lbSku]\"\n        },\n        \"loadBalancerTier\": {\n            \"type\": \"string\",\n            \"value\": \"[variables('moodleCommon').lbTier]\"\n        }\n    },\n    \"variables\": {\n        \"documentation01\": \"This main-template calls multiple sub-templates to create the moodle system\",\n        \"documentation02\": \"    recoveryservices0   - dummy template (see next statement)\",\n        \"documentation03\": \"    recoveryservices1   - creates a recovery vault that will be subsequently used by the VM Backup - a paramter swtich controls whethe is is called or bypassed\",\n        \"documentation04\": \"    redis               - creates a redis cache\",\n        \"documentation05\": \"    postgres / mysql  - creates a postgresql / mysql server\",\n        \"documentation06\": \"    vnet                - creates a virtual network with three subnets\",\n        \"documentation07\": \"    elastic             - creates a elastic search cluster on a vm farm\",\n        \"documentation08\": \"    gluster             - creates a gluster file system on a vm farm\",\n        \"documentation09\": \"    webvmss             - creates a vm scale set\",\n        \"documentation10\": \"    controller          - creates a controller VM and deploys code\",\n        \"documentation11\": \"GlusterFS Sizing guidance\",\n        \"moodleCommon\": {\n            \"baseTemplateUrl\": \"[concat(parameters('_artifactsLocation'), 'nested/')]\",\n            \"scriptLocation\": \"[concat(parameters('_artifactsLocation'), 'scripts/')]\",\n            \"artifactsSasToken\": \"[parameters('_artifactsLocationSasToken')]\",\n            \"appGwBePoolName\": \"[concat('appgw-bepool-', variables('resourceprefix'))]\",\n            \"appGwName\": \"[concat('appgw-', variables('resourceprefix'))]\",\n            \"appGwPipName\": \"[concat('appgw-pubip-',variables('resourceprefix'))]\",\n            \"appGwSslCertKeyVaultResourceId\": \"[parameters('appGwSslCertKeyVaultResourceId')]\",\n            \"appGwSslCertKeyVaultSecretName\": \"[parameters('appGwSslCertKeyVaultSecretName')]\",\n            \"appGwSkuCapacity\": \"[parameters('appGwSkuCapacity')]\",\n            \"appGwSkuName\": \"[parameters('appGwSkuName')]\",\n            \"appGwSkuTier\": \"[parameters('appGwSkuTier')]\",\n            \"applyScriptsSwitch\": \"[parameters('applyScriptsSwitch')]\",\n            \"autoscaleVmCountMax\": \"[parameters('autoscaleVmCountMax')]\",\n            \"autoscaleVmCountMin\": \"[parameters('autoscaleVmCountMin')]\",\n            \"autoscaleVmSku\": \"[parameters('autoscaleVmSku')]\",\n            \"azureBackupSwitch\": \"[parameters('azureBackupSwitch')]\",\n            \"azureSearchHostingMode\": \"[parameters('azureSearchHostingMode')]\",\n            \"azureSearchName\": \"[concat('azure-search-',variables('resourceprefix'))]\",\n            \"azureSearchNameHost\": \"[concat('azure-search-',variables('resourceprefix'),'.search.windows.net')]\",\n            \"azureSearchPartitionCount\": \"[parameters('azureSearchPartitionCount')]\",\n            \"azureSearchReplicaCount\": \"[parameters('azureSearchReplicaCount')]\",\n            \"azureSearchSku\": \"[parameters('azureSearchSku')]\",\n            \"commonFunctionsScriptUri\": \"[concat(parameters('_artifactsLocation'),'scripts/helper_functions.sh',parameters('_artifactsLocationSasToken'))]\",\n            \"controllerVmSku\": \"[parameters('controllerVmSku')]\",\n            \"customVnetId\": \"[parameters('customVnetId')]\",\n            \"ctlrNicName\": \"[concat('controller-vm-nic-',variables('resourceprefix'))]\",\n            \"ctlrNsgName\": \"[concat('controller-nsg-',variables('resourceprefix'))]\",\n            \"ctlrPipName\": \"[concat('controller-pubip-',variables('resourceprefix'))]\",\n            \"ctlrVmName\": \"[concat('controller-vm-',variables('resourceprefix'))]\",\n            \"vmssNsgName\": \"[concat('vmss-nsg-',variables('resourceprefix'))]\",\n            \"ctlrVmSecrets\": \"[take(variables('ctlrVmSecretsArray'), if(empty(parameters('keyVaultResourceId')), 0, 1))]\",\n            \"dbLogin\": \"[parameters('dbLogin')]\",\n            \"dbLoginPassword\": \"[concat(substring(uniqueString(resourceGroup().id, deployment().name), 2, 11), '*7', toUpper('pfiwb'))]\",\n            \"dbServerType\": \"[parameters('dbServerType')]\",\n            \"dbUsername\": \"[if(equals(parameters('dbServerType'), 'mysqlflex'), parameters('dbLogin'), concat(parameters('dbLogin'), '@', parameters('dbServerType'), '-', variables('resourceprefix')))]\",\n            \"ddosPlanName\": \"[concat('ddos-plan-',variables('resourceprefix'))]\",\n            \"ddosSwitch\": \"[parameters('ddosSwitch')]\",\n            \"elasticVmSku\": \"[parameters('elasticVmSku')]\",\n            \"elasticAvailabilitySetName\": \"[concat('elastic-avset-',variables('resourceprefix'))]\",\n            \"elasticClusterName\": \"[concat('es-cluster-',variables('resourceprefix'))]\",\n            \"elasticNicName1\": \"[concat('elastic-vm-nic-01-',variables('resourceprefix'))]\",\n            \"elasticNicName2\": \"[concat('elastic-vm-nic-02-',variables('resourceprefix'))]\",\n            \"elasticNicName3\": \"[concat('elastic-vm-nic-03-',variables('resourceprefix'))]\",\n            \"elasticScriptFilename\": \"install_elastic.sh\",\n            \"elasticVm1IP\": \"[concat( variables('octets')[0], '.', variables('octets')[1], '.', string(add(int(variables('octets')[2]),5)), '.20')]\",\n            \"elasticVm2IP\": \"[concat( variables('octets')[0], '.', variables('octets')[1], '.', string(add(int(variables('octets')[2]),5)), '.21')]\",\n            \"elasticVm3IP\": \"[concat( variables('octets')[0], '.', variables('octets')[1], '.', string(add(int(variables('octets')[2]),5)), '.22')]\",\n            \"elasticVmName\": \"[concat('elastic-vm-',variables('resourceprefix'))]\",\n            \"elasticVmName1\": \"[concat('elastic-vm-01-',variables('resourceprefix'))]\",\n            \"elasticVmName2\": \"[concat('elastic-vm-02-',variables('resourceprefix'))]\",\n            \"elasticVmName3\": \"[concat('elastic-vm-03-',variables('resourceprefix'))]\",\n            \"enableAccelNwForCtlrVmSwitch\": \"[parameters('enableAccelNwForCtlrVmSwitch')]\",\n            \"enableAccelNwForOtherVmsSwitch\": \"[parameters('enableAccelNwForOtherVmsSwitch')]\",\n            \"extBeName\": \"[concat('lb-backend-',variables('resourceprefix'))]\",\n            \"extFeName\": \"[concat('lb-frontend-',variables('resourceprefix'))]\",\n            \"extOutName001\": \"[concat('lb-outbound001-',variables('resourceprefix'))]\",\n            \"extOutName002\": \"[concat('lb-outbound002-',variables('resourceprefix'))]\",\n            \"extNatPool\": \"[concat('lb-natpool-',variables('resourceprefix'))]\",\n            \"extProbeHTTP\": \"[concat('lb-probe-http-',variables('resourceprefix'))]\",\n            \"extProbeHTTPS\": \"[concat('lb-probe-https-',variables('resourceprefix'))]\",\n            \"fileServerDiskCount\": \"[parameters('fileServerDiskCount')]\",\n            \"fileServerDiskSize\": \"[parameters('fileServerDiskSize')]\",\n            \"OSDiskSizeInGB\": \"[parameters('OSDiskSizeInGB')]\",\n            \"fileServerType\": \"[parameters('fileServerType')]\",\n            \"fileServerVmCount\": 2,\n            \"fileServerVmSku\": \"[parameters('fileServerVmSku')]\",\n            \"gatewayName\": \"[concat('vnet-gateway-',variables('resourceprefix'))]\",\n            \"gatewayPublicIPName\": \"[concat('vnet-gw-ip-',variables('resourceprefix'))]\",\n            \"gatewayType\": \"[parameters('gatewayType')]\",\n            \"gfsNameRoot\": \"[concat('gluster-vm-',variables('resourceprefix'))]\",\n            \"gfxAvailabilitySetName\": \"[concat('gluster-avset-',variables('resourceprefix'))]\",\n            \"glusterScriptFilename\": \"install_gluster.sh\",\n            \"htmlLocalCopySwitch\": \"[parameters('htmlLocalCopySwitch')]\",\n            \"httpsTermination\": \"[parameters('httpsTermination')]\",\n            \"installGdprPluginsSwitch\": \"[parameters('installGdprPluginsSwitch')]\",\n            \"installO365pluginsSwitch\": \"[parameters('installO365pluginsSwitch')]\",\n            \"installObjectFsSwitch\": \"[parameters('installObjectFsSwitch')]\",\n            \"lbDns\": \"[concat('lb-',variables('resourceprefix'),'.',parameters('location'),'.cloudapp.azure.com')]\",\n            \"lbSku\": \"[parameters('loadBalancerSku')]\",\n            \"lbTier\": \"[parameters('loadBalancerTier')]\",\n            \"lbName\": \"[concat('lb-',variables('resourceprefix'))]\",\n            \"lbOutName001\": \"[concat('lb-out001-',variables('resourceprefix'))]\",\n            \"lbOutName002\": \"[concat('lb-out002-',variables('resourceprefix'))]\",\n            \"lbPipName\": \"[concat('lb-pubip-',variables('resourceprefix'))]\",\n            \"lbOutPipName001\": \"[concat('lb-outpubip001-',variables('resourceprefix'))]\",\n            \"lbOutPipName002\": \"[concat('lb-outpubip002-',variables('resourceprefix'))]\",\n            \"location\": \"[parameters('location')]\",\n            \"moodleAdminPass\": \"[concat(toUpper('xl'), substring(uniqueString(resourceGroup().id, deployment().name), 6, 7),',1*8')]\",\n            \"moodleDbName\": \"[parameters('moodleDbName')]\",\n            \"moodleDbPass\": \"[concat('9#36^', substring(uniqueString(resourceGroup().id, deployment().name), 5, 8), toUpper('ercq'))]\",\n            \"moodleDbUser\": \"[parameters('moodleDbUser')]\",\n            \"moodleDbUserAzure\": \"[if(equals(parameters('dbServerType'), 'mysqlflex'), parameters('moodleDbUser'), concat(parameters('moodleDbUser'), '@', parameters('dbServerType'), '-', variables('resourceprefix')))]\",\n            \"moodleInstallScriptFilename\": \"install_moodle.sh\",\n            \"moodleOnAzureConfigsJsonPath\": \"/var/lib/cloud/instance/moodle_on_azure_configs.json\",\n            \"moodleVersion\": \"[parameters('moodleVersion')]\",\n            \"mssqlDbServiceObjectiveName\": \"[parameters('mssqlDbServiceObjectiveName')]\",\n            \"mssqlDbSize\": \"[parameters('mssqlDbSize')]\",\n            \"mssqlDbEdition\": \"[parameters('mssqlDbEdition')]\",\n            \"mssqlVersion\": \"[parameters('mssqlVersion')]\",\n            \"mysqlPgresSkuHwFamily\": \"[parameters('mysqlPgresSkuHwFamily')]\",\n            \"mysqlPgresSkuName\": \"[concat(if(equals(parameters('mysqlPgresSkuTier'),'Basic'),'B', if(equals(parameters('mysqlPgresSkuTier'),'GeneralPurpose'),'GP', 'MO')), '_', parameters('mysqlPgresSkuHwFamily'), '_', string(parameters('mysqlPgresVcores')))]\",\n            \"mysqlPgresSkuTier\": \"[parameters('mysqlPgresSkuTier')]\",\n            \"mysqlPgresStgSizeGB\": \"[parameters('mysqlPgresStgSizeGB')]\",\n            \"mysqlPgresVcores\": \"[parameters('mysqlPgresVcores')]\",\n            \"mysqlVersion\": \"[parameters('mysqlVersion')]\",\n            \"mysqlflexSkuName\": \"[parameters('mysqlflexSkuName')]\",\n            \"mysqlflexSkuTier\": \"[if(startsWith(parameters('mysqlflexSkuName'), 'Standard_B'), 'Burstable', if(startsWith(parameters('mysqlflexSkuName'), 'Standard_D'), 'GeneralPurpose', 'MemoryOptimized'))]\",\n            \"mysqlflexStgSizeGiB\": \"[parameters('mysqlflexStgSizeGiB')]\",\n            \"mysqlflexStgIops\": \"[parameters('mysqlflexStgIops')]\",\n            \"mysqlflexStgAutogrow\": \"[parameters('mysqlflexStgAutogrow')]\",\n            \"mysqlflexHaEnabled\": \"[parameters('mysqlflexHaEnabled')]\",\n            \"mysqlflexAvailabilityZone\": \"[parameters('mysqlflexAvailabilityZone')]\",\n            \"mysqlflexStandbyAvailabilityZone\": \"[parameters('mysqlflexStandbyAvailabilityZone')]\",\n            \"mysqlflexRequireSecureTransport\":  \"[if(equals(parameters('sslEnforcement'),'Enabled'),'ON','OFF')]\",\n            \"mysqlflexPrivateDnsZoneName\": \"[concat(parameters('dbServerType'), '-',variables('resourceprefix'),'.private.mysql.database.azure.com')]\",\n            \"nfsByoIpExportPath\": \"[parameters('nfsByoIpExportPath')]\",\n            \"nfsHaClientsIPRange\": \"[variables('subnetWebRange')]\",\n            \"nfsHaExportPath\": \"/drbd/data\",\n            \"nfsHaLbIP\": \"[concat(variables('subnetSanPrefix'), '.100')]\",\n            \"nfsHaNode0IP\": \"[concat(variables('subnetSanPrefix'), '.110')]\",\n            \"nfsHaNode1IP\": \"[concat(variables('subnetSanPrefix'), '.120')]\",\n            \"osDiskStorageType\": \"[parameters('osDiskStorageType')]\",\n            \"osType\": {\n                \"offer\": \"0001-com-ubuntu-server-jammy\",\n                \"publisher\": \"Canonical\",\n                \"sku\": \"[parameters('ubuntuVersion')]\",\n                \"version\": \"latest\"\n            },\n            \"phpVersion\": \"[parameters('phpVersion')]\",\n            \"policyName\": \"[concat('policy-',variables('resourceprefix'))]\",\n            \"postgresVersion\": \"[parameters('postgresVersion')]\",\n            \"redisCacheName\": \"[concat('redis-',variables('resourceprefix'))]\",\n            \"redisDeploySwitch\": \"[parameters('redisDeploySwitch')]\",\n            \"redisDns\": \"[concat('redis-',variables('resourceprefix'),'.redis.cache.windows.net')]\",\n            \"resourcesPrefix\": \"[variables('resourceprefix')]\",\n            \"searchType\": \"[parameters('searchType')]\",\n            \"serverName\": \"[concat(parameters('dbServerType'), '-',variables('resourceprefix'))]\",\n            \"siteURL\": \"[if(or(empty(parameters('siteURL')), equals(parameters('siteURL'), 'www.example.org')), concat(if(equals(parameters('httpsTermination'), 'AppGw'),'appgw-','lb-'),variables('resourceprefix'),'.',parameters('location'),'.cloudapp.azure.com'), parameters('siteURL'))]\",\n            \"sshPublicKey\": \"[parameters('sshPublicKey')]\",\n            \"sshUsername\": \"[parameters('sshUsername')]\",\n            \"sslEnforcement\": \"[parameters('sslEnforcement')]\",\n            \"storageAccountName\": \"[tolower(concat('abs',variables('resourceprefix')))]\",\n            \"storageAccountType\": \"[parameters('storageAccountType')]\",\n            \"subnetDb\": \"[concat('db-subnet-',variables('resourceprefix'))]\",\n            \"subnetDbPrefix\": \"[concat(variables('octets')[0], '.', variables('octets')[1], '.', string(add(int(variables('octets')[2]),7)))]\",\n            \"subnetDbRange\": \"[concat(variables('octets')[0], '.', variables('octets')[1], '.', string(add(int(variables('octets')[2]),7)), '.0/24')]\",\n            \"subnetAppGw\": \"[concat('appgw-subnet-',variables('resourceprefix'))]\",\n            \"subnetAppGwPrefix\": \"[concat(variables('octets')[0], '.', variables('octets')[1], '.', string(add(int(variables('octets')[2]),6)))]\",\n            \"subnetAppGwRange\": \"[concat(variables('octets')[0], '.', variables('octets')[1], '.', string(add(int(variables('octets')[2]),6)), '.0/24')]\",\n            \"subnetElastic\": \"[concat('elastic-subnet-',variables('resourceprefix'))]\",\n            \"subnetElasticPrefix\": \"[concat( variables('octets')[0], '.', variables('octets')[1], '.', string(add(int(variables('octets')[2]),5)))]\",\n            \"subnetElasticRange\": \"[concat( variables('octets')[0], '.', variables('octets')[1], '.', string(add(int(variables('octets')[2]),5)), '.0/24')]\",\n            \"subnetGateway\": \"GatewaySubnet\",\n            \"subnetGatewayPrefix\": \"[concat(variables('octets')[0], '.', variables('octets')[1], '.', string(add(int(variables('octets')[2]),4)))]\",\n            \"subnetGatewayRange\": \"[concat(variables('octets')[0], '.', variables('octets')[1], '.', string(add(int(variables('octets')[2]),4)), '.0/24')]\",\n            \"subnetRedis\": \"[concat('redis-subnet-',variables('resourceprefix'))]\",\n            \"subnetRedisPrefix\": \"[concat( variables('octets')[0], '.', variables('octets')[1], '.', string(add(int(variables('octets')[2]),3)))]\",\n            \"subnetRedisRange\": \"[concat( variables('octets')[0], '.', variables('octets')[1], '.', string(add(int(variables('octets')[2]),3)), '.0/24')]\",\n            \"subnetSan\": \"[concat('san-subnet-',variables('resourceprefix'))]\",\n            \"subnetSanPrefix\": \"[variables('subnetSanPrefix')]\",\n            \"subnetSanRange\": \"[concat( variables('octets')[0], '.', variables('octets')[1], '.', string(add(int(variables('octets')[2]),2)), '.0/24')]\",\n            \"subnetTika\": \"[concat('tika-subnet-',variables('resourceprefix'))]\",\n            \"subnetTikaPrefix\": \"[concat( variables('octets')[0], '.', variables('octets')[1], '.', string(add(int(variables('octets')[2]),1)))]\",\n            \"subnetTikaRange\": \"[concat( variables('octets')[0], '.', variables('octets')[1], '.', string(add(int(variables('octets')[2]),1)), '.0/24')]\",\n            \"subnetWeb\": \"[concat('web-subnet-',variables('resourceprefix'))]\",\n            \"subnetWebPrefix\": \"[concat( variables('octets')[0], '.', variables('octets')[1], '.', string(add(int(variables('octets')[2]),0)))]\",\n            \"subnetWebRange\": \"[variables('subnetWebRange')]\",\n            \"thumbprintSslCert\": \"[if(or(empty(parameters('keyVaultResourceId')), empty(parameters('sslCertThumbprint'))), 'None', parameters('sslCertThumbprint'))]\",\n            \"thumbprintCaCert\": \"[if(or(empty(parameters('keyVaultResourceId')), empty(parameters('caCertThumbprint'))), 'None', parameters('caCertThumbprint'))]\",\n            \"tikaNicName\": \"[concat('tika-vm-nic-',variables('resourceprefix'))]\",\n            \"tikaScriptFilename\": \"install_tika.sh\",\n            \"tikaService\": \"[parameters('tikaService')]\",\n            \"tikaVmIP\": \"[if(equals(parameters('tikaService'), 'tika'), concat( variables('octets')[0], '.', variables('octets')[1], '.', string(add(int(variables('octets')[2]),1)), '.20'), 'none')]\",\n            \"tikaVmName\": \"[concat('tika-vm-',variables('resourceprefix'))]\",\n            \"tikaVmSku\": \"[parameters('tikaVmSku')]\",\n            \"vNetAddressSpace\": \"[parameters('vNetAddressSpace')]\",\n            \"vaultName\": \"[concat('vault-',variables('resourceprefix'))]\",\n            \"vmssName\": \"[concat('vmss-',variables('resourceprefix'))]\",\n            \"vmssdStorageAccounttName\": \"[concat('vmss',uniqueString(resourceGroup().id))]\",\n            \"vnetDbDeploySwitch\": \"[and(parameters('vnetDbDeploySwitch'), equals(parameters('dbServerType'), 'mysqlflex'))]\",\n            \"vnetGwDeploySwitch\": \"[parameters('vnetGwDeploySwitch')]\",\n            \"vnetName\": \"[concat('vnet-',variables('resourceprefix'))]\",\n            \"vpnType\": \"[parameters('vpnType')]\",\n            \"webServerSetupScriptFilename\": \"setup_webserver.sh\",\n            \"webServerType\": \"[parameters('webServerType')]\",\n            \"isMigration\": \"[parameters('isMigration')]\"\n        },\n        \"certUrlArray\": [\n            {\n                \"certificateUrl\": \"[parameters('sslCertKeyVaultURL')]\"\n            },\n            {\n                \"certificateUrl\": \"[parameters('caCertKeyVaultURL')]\"\n            }\n        ],\n        \"ctlrVmSecretsArray\": [\n            {\n                \"sourceVault\": {\n                    \"id\": \"[parameters('keyVaultResourceId')]\"\n                },\n                \"vaultCertificates\": \"[take(variables('certUrlArray'), if(empty(parameters('caCertKeyVaultURL')), 1, 2))]\"\n            }\n        ],\n        \"octets\": \"[split(parameters('vNetAddressSpace'), '.')]\",\n        \"resourceprefix\": \"[substring(uniqueString(resourceGroup().id, deployment().name), 3, 6)]\",\n        \"subnetSanPrefix\": \"[concat( variables('octets')[0], '.', variables('octets')[1], '.', string(add(int(variables('octets')[2]),2)))]\",\n        \"subnetWebRange\": \"[concat( variables('octets')[0], '.', variables('octets')[1], '.', string(add(int(variables('octets')[2]),0)), '.0/24')]\"\n    }\n}\n"
  },
  {
    "path": "azuredeploy.parameters.json",
    "content": "{\n    \"$schema\": \"https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#\",\n    \"contentVersion\": \"1.0.0.0\",\n        \"parameters\": {\n                \"sshPublicKey\":                { \"value\": \"GEN-SSH-PUB-KEY\" },\n                \"fileServerDiskCount\":         { \"value\": 2 },\n                \"fileServerDiskSize\":          { \"value\": 32 }\n        }\n}\n"
  },
  {
    "path": "docs/Cleanup.md",
    "content": "# Cleanup All Resources\n\nTo cleanup a Moodle deployment simply delete the Resource Group that\ncontains it. The commands below will iterate over your workspace\ndirectory and delete all deployments.\n\n## Prerequisites\n\nFirst we need to ensure our [environment variables](./Environment-Variables.md) are correctly configured.\n\n## Remove each resource group\n\nThis command will delete all resources in *all* resource groups. Run\nwith caution.\n\nNote, that this command will not fully delete the resource group if\nyou have Azure Backup enabled since the Recovery Services Vault will\nnot be deleted (it's got the backups of you data!).\n\n``` bash\nfor filename in $MOODLE_AZURE_WORKSPACE/*; do az group delete --yes --name $(basename $filename) --no-wait; done\n```\n"
  },
  {
    "path": "docs/Deploy.md",
    "content": "# Deploy Autoscaling Moodle Stack to Azure\n\nAfter following the steps in this this document you with awill have a\nnew Moodle site with caching for speed and scaling frontends to handle\nload. The filesystem behind it is mirrored for high availability and\noptionally backed up through Azure. Filesystem permissions and options\nhave also been tuned to make Moodle more secure than a default\ninstall.\n\n## Prerequisites\n\nTo make things consitent across different sessions managing Moodle we\nshould [configure the environment](./Preparation.md).\n\n\n## Create Resource Group\n\nWhen you create the Moodle cluster you will create many resources. On\nAzure it is a best practice to collect such resources together in a\nResource Group. The first thing we need to do, therefore, is create a\nresource group:\n\n```\naz group create --name $MOODLE_RG_NAME --location $MOODLE_RG_LOCATION\n```\n\nResults:\n\n```expected_similarity=0.4\n{\n  \"id\": \"/subscriptions/325e7c34-99fb-4190-aa87-1df746c67705/resourceGroups/rgmoodlearm3\",\n  \"location\": \"westus2\",\n  \"managedBy\": null,\n  \"name\": \"rgmoodlearm3\",\n  \"properties\": {\n    \"provisioningState\": \"Succeeded\"\n  },\n  \"tags\": null\n}\n```\n\n## Create Azure Deployment Parameters\n\nYour deployment will be configured using an\n`azuredeploy.parameters.json` file. It is possible to provide these\nparameters interactively via the command line by simply omitting the\nparamaters file in the command in the next section. However, it is\nmore reproducible if we use a paramaters file.\n\nA good set of defaults are provided in the git repository. These\ndefaults create a scalable cluster that is suitable for low volume\ntesting. If you are building out a production service you should\nreview the section below on sizing considerations. For now we will\nproceed with the defaults, but there is one value, the `sshPublicKey`\nthat **must** be provided. The following command will replace the\nplaceholder in the parameters template file with an SSH key used for\ntesting puporses (this is created as part of the envrionment setup in\nthe prerequisites):\n\n``` bash\nssh_pub_key=`cat $MOODLE_SSH_KEY_FILENAME.pub`\necho $ssh_pub_key\nsed \"s|GEN-SSH-PUB-KEY|$ssh_pub_key|g\" $MOODLE_AZURE_WORKSPACE/arm_template/azuredeploy.parameters.json > $MOODLE_AZURE_WORKSPACE/$MOODLE_RG_NAME/azuredeploy.parameters.json\n```\n\nIf you'd like to configure the Moodle cluster (to be deployed)\nwith your own SSL certificate for your domain (siteURL) at the\ndeployment time, you can do so by using [Azure Key Vault](https://azure.microsoft.com/en-us/services/key-vault/)\nand following the instructions in the [SSL cert documentation](SslCert.md).\n\nFor more information see the [parameters documentation](Parameters.md).\n\n## Deploy cluster\n\nNow that we have a resource group and a configuration file we can\ncreate the cluster itself. This is done with a single command:\n\n```\naz deployment group create --name $MOODLE_DEPLOYMENT_NAME --resource-group $MOODLE_RG_NAME --template-file $MOODLE_AZURE_WORKSPACE/arm_template/azuredeploy.json --parameters $MOODLE_AZURE_WORKSPACE/$MOODLE_RG_NAME/azuredeploy.parameters.json\n```\n\n## Using the created stack\n\nIn testing, stacks typically took between 1 and 2 hours to finish,\ndepending on spec. Once complete you will receive a JSON output\ncontaining information needed to manage your Moodle install (see\n`outputs`). You can also retrieve this infromation from the portal or\nthe CLI.\n                      \nOnce Moodle has been created, and (where necessary) you have\nconfigured your custom `siteURL` DNS to point to the\n`loadBalancerDNS`, you should be able to load the `siteURL` in a\nbrowser and login with the username \"admin\" and the\n`moodleAdminPassword`. Note that the values for each of these\nparameters are avialble in the portal or the `outputs` section of the\nJSON response from the previous deploy command. See [documentation on\nhow to retrieve configuration data](./Get-Install-Data.md) along\nwith full details of all the output parameters avialble to you.\n\nNote that by default the deployment uses a self-signed certificate,\nconsequently you will recieve a warning when accessing the site. To\nadd a genuine certificate see the documentation on [managing your\ncluster](./Manage.md).\n\n## Sizing Considerations and Limitations\n\nDepending on what you're doing with Moodle you will want to configure\nyour deployment appropriately.The defaults included produce a cluster\nthat is inexpensive but probably too low spec to use beyond simple\ntesting scenarios. This section includes an overview of how to size\nthe database and VM instances for your use case.\n\n### Database Sizing\n\nAs of the time of this writing, Azure supports \"Basic\", \"General Purpose\" and \"Memory Optimized\"\ntiers for MySQL/PostgreSQL database instances. In addition the mysqlPgresVcores defines\nthe number of vCores for each DB server instance, and the number of those you can use is limited by\ndatabase tier:\n\n- Basic: 1, 2\n- General Purpose: 2, 4, 8, 16, 32\n- Memory Optimized: 2, 4, 8, 16\n\nThis value also limits the maximum number of connections, as defined\nhere: https://docs.microsoft.com/en-us/azure/mysql/concepts-limits\n\nAs the Moodle database will handle cron processes as well as the\nwebsite, any public facing website with more than 10 users will likely\nrequire upgrading to 100. Once the site reaches 30+ users it will\nrequire upgrading to General Purpose for more compute units. This depends\nentirely on the individual site. As MySQL databases cannot change (or\nbe restored to a different tier) once deployed it is a good idea to\nslightly overspec your database.\n\nAll MySQL/PostgreSQL database storage, regardless of tier, has a hard upper limit of 1\nterabyte (1024 GB), starting from 5 GB minimum, increasing by 1 GB. You gain additional iops for each added GB, so if\nyou're expecting a heavy amount of traffic you will want to oversize\nyour storage. The current maximum iops with a 1TB disk is 3000.\n\n### Controller instance sizing\n\nThe controller handles both syslog and cron duties. Depending on how\nbig your Moodle cron runs are this may not be sufficient. If cron jobs\nare very delayed and cron processes are building up on the controller\nthen an upgrade in tier is needed.\n\n### Frontend instances\n\nIn general the frontend instances will not be the source of any\nbottlenecks unless they are severely undersized versus the rest of the\ncluster. More powerful instances will be needed should fpm processes\nspawn and exhaust memory during periods of heavy site load. This can\nalso be mitigated against by increasing the number of VMs but spawning\nnew VMs is slower (and potentially more expensive) than having that\ncapacity already available.\n\nIt is worth noting that the memory allowances on these instances allow\nfor more memory than they may be able to provide with lower instance\ntiers. This is intentional as you can opt to run larger VMs with more\nmemory and not require manual configuration. FPM also allows for a\nvery large number of threads which prevents the system from failing\nduring many small jobs.\n\n\n## Next Steps\n\n  1. [Retrieve configuration details using CLI](./Get-Install-Data.md)\n  1. [Manage the Moodle cluster](./Manage.md)\n"
  },
  {
    "path": "docs/Environment-Variables.md",
    "content": "# Environment Variables\n\nIn order to configure our deployment and tools we'll set up some\nenvironment variables to ensure consistency. If you are running these\nscripts through SimDem you can customize these values by copying and\nediting `env.json` into `env.local.json`.\n\nWe'll need a unique name for our Resource Group in Azure, but when\nrunning in an automated mode it is useful to have a (mostly) unique\nname for your deployment and related resources. We'll use a timestamp.\nIf the environmnt variable `MOODLE_RG_NAME` is not set we will\ncreate a new value using a timestamp:\n\n\n``` shell\nif [ -z \"$MOODLE_RG_NAME\" ]; then MOODLE_RG_NAME=moodle_$(date +%Y-%m-%d-%H); fi\n```\n\nOther configurable values for our Azure deployment include the\nlocation and depoloyment name. We'll standardize these, but you can\nuse different values if you like.\n\n``` shell\nMOODLE_RG_LOCATION=southcentralus\nMOODLE_DEPLOYMENT_NAME=MasterDeploy\n```\n\nWe also need to provide an SSH key. Later we'll generate this if it\ndoesn't already exist but to enable us to reuse an existing key we'll\nstore it's filename in an Environment Variable.\n\n``` shell\nMOODLE_SSH_KEY_FILENAME=~/.ssh/moodle_id_rsa\n```\n\nWe need a workspace for storing configuration files and other\nper-deployment artifacts:\n\n``` shell\nMOODLE_AZURE_WORKSPACE=~/.moodle\n```\n\n## Create Workspace\n\nEnsure the workspace for this particular deployment exists:\n\n```\nmkdir -p $MOODLE_AZURE_WORKSPACE/$MOODLE_RG_NAME\n```\n\n## Validation\n\nAfter working through this file there should be a number of\nenvironment variables defined that will be used to provide a common\nsetup for all our Moodle on Azure work.\n\nThe resource group name defines the name of the group into which all\nresources will be, or are, deployed. \n\n```bash\necho \"Resource Group for deployment: $MOODLE_RG_NAME\"\n```\n\nResults:\n\n```\nResource Group for deployment: southcentralus\n```\n\nThe resource group location is:\n\n```bash\necho \"Deployment location: $MOODLE_RG_LOCATION\"\n```\n\nResults:\n\n```\nDeployment location: southcentralus\n```\n\nWhen deploying a Moodle cluster the deployment will be given a name so\nthat it can be identified later should it be neceessary to debug.\n\n\n```bash\necho \"Deployment name: $MOODLE_DEPLOYMENT_NAME\"\n```\n\nResults:\n\n```\nDeployment name: MasterDeploy\n```\n\nThe SSH key to use can be found in a file, if necessary this will be\ncreated as part of these scripts.\n\n``` shell\necho \"SSH key filename: $MOODLE_SSH_KEY_FILENAME\"\n```\n\nResults:\n\n```\nSSH key filename: ~/.ssh/moodle_id_rsa\n```\n\nConfiguration files will be written to / read from a customer directory:\n\n``` shell\necho \"Workspace directory: $MOODLE_AZURE_WORKSPACE\"\n```\n\nResults:\n\n```\nWorkspace directory: ~/.moodle\n```\n\nEnsure the workspace directory exists:\n\n\n``` bash\nif [ ! -f \"$MOODLE_AZURE_WORKSPACE/$MOODLE_RG_NAME\" ]; then echo \"Worspace exists\"; fi\n```\n\nResults:\n\n```\nWorkspace exists\n```\n"
  },
  {
    "path": "docs/Get-Install-Data.md",
    "content": "# Retrieve essential install details\n\nOnce a deployment has completed the ARM template will output some\nvalues that you will need for managing your Moodle instalation. These\nare available in the portal, but in this document we will retrieve\nthem using the AZ command line tools and through the AZ CLI tool. This\ndocument describes the available parameters and how to retrieve them.\n\n## Prerequisites\n\nIn order to configure our deployment and tools we'll set up some\n[environment variables](./Environment-Variables.md) to ensure consistency.\n\n## Output Paramater Overview\n\nThe available output parameters are:\n\n  - **siteURL**: If you provided a `siteURL` parameter when deploying this\n    will be set to the supplied value. Otherwise it will be the same as\n    the loadBalancerDNS, see below.\n  - **loadBalancerDNS**: This is the DNS name of your application load\n    balancer. If you provided a `siteURL` parameter when deploying\n    you'll need to add a DNS entry to its CNAMEs pointing to this address.\n  - **moodleAdminPassword**: The generated password for the \"admin\" user\n    in your Moodle install.\n  - **controllerInstanceIP**: This is the IP address of the controller\n    Virtual Machine. You will need to SSH into this to make changes to\n    your Moodle code or view logs.\n  - **databaseDNS**: This is the public DNS of your database instance. If\n    you wish to set up local backups or access the DB directly, you'll\n    need to use this.\n  - **databaseAdminUsername**: The admin username for your database\n    (this is not the same as your Moodle username).\n  - **databaseAdminPassword**: The admin password for your\n    database (this is not the same as your Moodle password).\n\n## Retrieving Output Parameters Using the CLI\n\nTo get a complete list of outputs in json format use:\n\n```bash\naz deployment group show --resource-group $MOODLE_RG_NAME --name $MOODLE_DEPLOYMENT_NAME --out json --query *.outputs\n```\n\nIndividual outputs can be retrieved by filtering, for example, to get\njust the value of the `siteURL` use:\n\n``` bash\naz deployment group show --resource-group $MOODLE_RG_NAME --name $MOODLE_DEPLOYMENT_NAME --out json --query *.outputs.siteURL.value\n```\n\nHowever, since we are reqeusting JSON output (the default) the value\nis enclosed in quotes. In order to remove these we can output as a tab\nseparated list (TSV):\n\n``` bash\naz deployment group show --resource-group $MOODLE_RG_NAME --name $MOODLE_DEPLOYMENT_NAME --out tsv --query *.outputs.siteURL\n```\n\nNow we can assign individual values to environment variables, for example:\n\n``` bash\nMOODLE_SITE_URL=\"$(az deployment group show --resource-group $MOODLE_RG_NAME --name $MOODLE_DEPLOYMENT_NAME --out tsv --query *.outputs.siteURL.value)\"\n```\n\n### Retrieving Moodle Site URL\n\nThe Site URL is the value used to configure Moodle's base URL. The\nsite URL can be provided as an input to the template via the parameter\n`siteURL`, in which case you will not need to retrieve this from the\noutputs. However, if you do not define this, or if you leave it as the\ndefault \"www.example.org\" you will need to retrieve this value from\nAzure using the following command:\n\n```bash\nMOODLE_SITE_URL=\"$(az deployment group show --resource-group $MOODLE_RG_NAME --name $MOODLE_DEPLOYMENT_NAME --out tsv --query *.outputs.siteURL.value)\"\n```\n\n#### Retrieving Moodle Site Load Balancer URL\n\nThe load balancer DNS is the publicly registered DNS name for your\nMoodle DNS. If this is different from the site URL it is important to\nensure that you configure your DNS entry for site URL to point at the\nload balancer.\n\n```bash\nMOODLE_LOAD_BALANCER_DNS=\"$(az deployment group show --resource-group $MOODLE_RG_NAME --name $MOODLE_DEPLOYMENT_NAME --out tsv --query *.outputs.loadBalancerDNS.value)\"\n```\n\n### Retrieving Moodle Administrator Password\n\nMoodle admin password (username is \"admin\"):\n\n```bash\nMOODLE_ADMIN_PASSWORD=\"$(az deployment group show --resource-group $MOODLE_RG_NAME --name $MOODLE_DEPLOYMENT_NAME --out tsv --query *.outputs.moodleAdminPassword.value)\"\n```\n\n### Retriving Controller Virtual Machine Details\n\nThe controller VM runs management tasks for the cluster, such as cron jobs and syslog.\n\n```bash\nMOODLE_CONTROLLER_INSTANCE_IP=\"$(az deployment group show --resource-group $MOODLE_RG_NAME --name $MOODLE_DEPLOYMENT_NAME --out tsv --query *.outputs.controllerInstanceIP.value)\"\n```\n\nThere is no username and password for this VM since a username and SSH\nkey are provided as input parameters to the template.\n\n### Retreiving Database Information\n\n#### Database URL\n\n``` bash\nMOODLE_DATABASE_DNS=\"$(az deployment group show --resource-group $MOODLE_RG_NAME --name $MOODLE_DEPLOYMENT_NAME --out tsv --query *.outputs.databaseDNS.value)\"\n```\n#### Database admin username\n\n``` bash\nMOODLE_DATABASE_ADMIN_USERNAME=\"$(az deployment group show --resource-group $MOODLE_RG_NAME --name $MOODLE_DEPLOYMENT_NAME --out tsv --query *.outputs.databaseAdminUsername.value)\"\n```\n\n#### Database admin password\n\n``` bash\nMOODLE_DATABASE_ADMIN_PASSWORD=\"$(az deployment group show --resource-group $MOODLE_RG_NAME --name $MOODLE_DEPLOYMENT_NAME --out tsv --query *.outputs.databaseAdminPassword.value)\"\n```\n\n### Retrieving Moodle Application VNET Information\n\nFirst frontend VM IP:\n\n``` bash\nMOODLE_FIRST_FRONTEND_VM_IP=\"$(az deployment group show --resource-group $MOODLE_RG_NAME --name $MOODLE_DEPLOYMENT_NAME --out tsv --query *.outputs.firstFrontendVmIP.value)\"\n```\n\n# Validation\n\nAfter having run each of the commands in this document you should have\neach of the output parameters available in environment variable:\n\n``` bash\necho $MOODLE_SITE_URL\necho $MOODLE_LOAD_BALANCER_DNS\necho $MOODLE_ADMIN_PASSWORD\necho $MOODLE_CONTROLLER_INSTANCE_IP\necho $MOODLE_DATABASE_DNS\necho $MOODLE_DATABASE_ADMIN_USERNAME\necho $MOODLE_DATABASE_ADMIN_PASSWORD\necho $MOODLE_FIRST_FRONTEND_VM_IP\n```\n\n## Next Steps\n\n  1. [Manage the Moodle cluster](./Manage.md)\n"
  },
  {
    "path": "docs/Manage.md",
    "content": "# Managing a Scalable Moodle Cluster in Azure\n\nThis document provides an overview of how to perform various\nmanagement tasks on a scalable Moodle cluster on Azure.\n\n## Prerequisites\n\nIn order to configure our deployment and tools we'll set up some\n[environment variables](./Environment-Variables.md) to ensure consistency.\n\nIn order to manage a cluster it is clearly necessary to first [deploy\na scalable Moodle cluster on Azure](./Deploy.md).\n\nFor convenience and readability this document also assumes that essential [deployment details for your cluster have been assigned to environment variables](./Get-Install-Data.md).\n\n## Updating Moodle code/settings\n\nYour controller Virtual Machine has Moodle code and data stored in\n`/moodle`. The site code is stored in `/moodle/html/moodle/`. This\ndata is replicated across dual gluster nodes to provide high\navailability. This directory is also mounted to your autoscaled\nfrontends so all changes to files on the controller VM are immediately\navailable to all frontend machines (when the `htmlLocalCopySwitch` in `azuredeploy.json`\nis false--otherwise, see below). Note that any updates on Moodle code/settings\n(e.g., additional plugin installations, Moodle version upgrade) have to be done\non the controller VM using shell commands, not through a web browser, because the\nHTML directory's permission is read-only for the web frontend VMs (thus any web-based\nMoodle code updates will fail).\n\nDepending on how large your Gluster disks are sized, it may be helpful\nto keep multiple older versions (/moodle/html1, /moodle/html2, etc) to\nroll back if needed.\n\nTo connect to your Controller VM use SSH with a username of\n'azureuser' and the SSH provided in the `sshPublicKey` input\nparameter. For example, to retrieve a listing of files and directories\nin the `/moodle` directory use:\n\n```\nssh -o StrictHostKeyChecking=no azureadmin@$MOODLE_CONTROLLER_INSTANCE_IP ls -l /moodle\n```\n\nResults:\n\n```\nWarning: Permanently added '52.228.45.38' (ECDSA) to the list of known hosts.\ntotal 12\ndrwxr-xr-x  2 www-data www-data 4096 Jan 17 00:59 certs\n-rw-r--r--  1 root     root        0 Jan 17 02:22 db-backup.sql\ndrwxr-xr-x  3 www-data www-data 4096 Jan 17 00:54 html\ndrwxrwx--- 10 www-data www-data 4096 Jan 17 06:55 moodledata\n```\n\n**IMPORTANT NOTE**\n\nIt is important to realize that the `-o StrictHostKeyChecking=no`\noption in the above SSH command presents a security risk. It is\nincluded here to facilitate automated validation of these commands. It\nis not recommended to use this option in production environments,\ninstead run the command manually and validate the host key.\nSubsequent executions of an SSH command will not require this\nvalidation step. For more information there is an excellent\n[superuser.com\nQ&A](https://superuser.com/questions/421074/ssh-the-authenticity-of-host-host-cant-be-established/421084#421084).\n\n### If you set `htmlLocalCopySwitch` to true (this is the default now)\n\nOriginally the `/moodle/html` directory was shared across all autoscaled\nweb VMs through the specified file server (Gluster or NFS), and this is\nnot good for web response time. Therefore, we introduced the\n`htmlLocalCopySwitch` that'll copy the `/moodle/html` directory to\n`/var/www/html` in each autoscaled web VM and reconfigures the web\nserver (apache/nginx)'s server root directory accordingly, when it's set\nto true. This now requires directory sync between `/moodle/html` and\n`/var/www/html`, and currently it's addressed by simple polling\n(minutely). Therefore, if you are going to update your Moodle\ncode/settings with the switch set to true, please follow the\nfollowing steps:\n\n* Put your Moodle site to maintenance mode.\n  * This will need to be done on the contoller VM with some shell command.\n  * It should be followed by running the following command to propagate the change to all autoscaled web VMs:\n    ```bash\n    $ sudo /usr/local/bin/update_last_modified_time.moodle_on_azure.sh\n    ```\n  * Once this command is executed, each autoscaled web VM will pick up (sync) the changes within 1 minute, so wait for one minute.\n* Then you can start updating your Moodle code/settings, like installing/updating plugins or upgrading Moodle version or changing Moodle configurations. Again, note that this should be all done on the controller VM using some shell commands.\n* When you are done updating your Moodle code/settings, run the same command as above to let each autoscaled web VM pick up (sync) the changes (wait for another minute here, for the same reason).\n\nPlease do let us know on this Github repo's Issues if you encounter any problems with this process.\n\n## Getting an SQL dump\n\nBy default a daily sql dump of your database is taken at 02:22 and\nsaved to `/moodle/db-backup.sql`(.gz). This file can be retrieved\nusing SCP or similar. For example:\n\n``` bash\nscp azureadmin@$MOODLE_CONTROLLER_INSTANCE_IP:/moodle/db-backup.sql /tmp/moodle-db-backup.sql\n```\n\nTo obtain a more recent SQL dump you run the commands appropriate for\nyour chosen database on the Controller VM. The following sections will\nhelp with this task.\n\n#### Postgres\n\nPostgress provides a `pg_dump` command that can be used to take a\nsnapshot of the database via SSH. For example, use the following\ncommand:\n\n``` bash\nssh azureadmin@$MOODLE_CONTROLLER_INSTANCE_IP 'pg_dump -Fc -h $MOODLE_DATABASE_DNS -U $MOODLE_DATABASE_ADMIN_USERNAME moodle > /moodle/db-snapshot.sql'\n```\n\nSee the Postgres documentation for full details of the [`pg_dump`](https://www.postgresql.org/docs/9.5/static/backup-dump.html) command.\n\n#### MySQL\n\nMySQL provides a `mysql_dump` command that can be used to take a\nsnapshot of the database via SSH. For example, use the following\ncommand:\n\n``` bash\nssh azureadmin@$MOODLE_CONTROLLER_INSTANCE_IP 'mysqldump -h $mysqlIP -u ${azuremoodledbuser} -p'${moodledbpass}' --databases ${moodledbname} | gzip > /moodle/db-backup.sql.gz'\n```\n\n## Backup and Recovery\n\nIf you have set the `azureBackupSwitch` in the input parameters to `1`\nthen Azure will provide VM backups of your Gluster node. This is\nrecommended as it contains both your Moodle code and your sitedata.\nRestoring a backed up VM is outside the scope of this doc, but Azure's\ndocumentation on Recovery Services can be found here:\nhttps://docs.microsoft.com/en-us/azure/backup/backup-azure-vms-first-look-arm\n\n## Resizing your Database\n\nNote: This process involves site downtime and should therefore only be\ncarried out during a planned maintenance window.\n\nAt the time of writing Azure does not support resizing MySQL or\nPostgres databases. You can, however, create a new database instance,\nwith a different size, and change your config to point to that. To get\na different size database you'll need to:\n\n  1. [Place your Moodle site into maintenance\n     mode](https://docs.moodle.org/34/en/Maintenance_mode). You can do\n     this either via the web interface or the command line on the\n     controller VM.\n  2. Perform an SQL dump of your database. See above for more details.\n  3. Create a new Azure database of the size you want inside your\n     existing resource group.\n  4. Using the details in your /moodle/html/moodle/config.php create a\n     new user and database matching the details in config.php. Make\n     sure to grant all rights on the db to the user.\n  5. On the controller instance, change the db setting in\n     /moodle/html/moodle/config.php to point to the new database.\n  6. Take Moodle site out of maintenance mode.\n  7. Once confirmed working, delete the previous database instance.\n\nHow long this takes depends entirely on the size of your database and\nthe speed of your VM tier. It will always be a large enough window to\nmake a noticeable outage.\n\n## Changing the SSL cert\n\nThe self-signed cert generated by the template is suitable for very\nbasic testing, but a public website will want a real cert. After\npurchasing a trusted certificate, it can be copied to the following\nfiles to be ready immediately:\n\n  - /moodle/certs/nginx.key: Your certificate's private key\n  - /moodle/certs/nginx.crt: Your combined signed certificate and trust chain certificate(s).\n\n## Managing Azure DDoS protection\n\nBy default, every plublic IP is protected by Azure DDoS protection Basic SKU. \nYou can find more information about Azure DDoS protection Basic SKU [here](https://docs.microsoft.com/en-us/azure/virtual-network/ddos-protection-overview).\n\nIf you want more protection, you can activate Azure DDoS protection Standard SKU by setting \nthe ddosSwith to true. You can find how to work with Azure DDoS \nprotection plan [here](https://docs.microsoft.com/en-us/azure/virtual-network/manage-ddos-protection#work-with-ddos-protection-plans).\n\nIf you want to disable the Azure DDoS protection, you can follow the instruction \n[here](https://docs.microsoft.com/en-us/azure/virtual-network/manage-ddos-protection#disable-ddos-for-a-virtual-network). \n\nBe careful, disabling the Azure DDoS protection on your vnet will not stop the fee.\nYou have to delete the Azure DDoS protection plan if you want to stop the fee.\n\nIf you have deployed your cluster without Azure DDoS protection plan, you still can activate the \nAzure DDoS protection plan thanks to the instruction [here](https://docs.microsoft.com/en-us/azure/virtual-network/manage-ddos-protection#enable-ddos-for-an-existing-virtual-network).\n\n## Next Steps\n\n  1. [Retrieve configuration details using CLI](./Get-Install-Data.md)\n"
  },
  {
    "path": "docs/Parameters.md",
    "content": "# Moodle on Azure Parameters\n\nOur goal with these templates is to make it as easy as possible to\ndeploy a Moodle on Azure cluster that can be customized to your\nspecific needs. To that end we provide a great manay configuration\noptions. This document attempts to document all these parameters,\nhowever, like all documentation it can sometimes fall behind. For a\ncanonical reference you should review the `azuredeploy.json` file.\n\n## Extracting documentation from azuredeploy.json\n\nTo make it a litte easier to read `azuredeploy.json` you might want to\nrun the following commands which will extract the necessary\ninformation and display it in a more readable form.\n\n```bash\nsudo apt install jq\n```\n\n``` bash\njq -r '.parameters | to_entries[] | \"### \" + .key + \"\\n\\n\" + .value.metadata.description + \"\\n\\nType: \" + .value.type + \"\\n\\nPossible Values: \" + (.value.allowedValues | @text) + \"\\n\\nDefault: \" + (.value.defaultValue | @text) + \"\\n\\n\"' azuredeploy.json\n```\n\n## Available Parameters\n\n### _artifactsLocation\n\nThe base URI where artifacts required by this template are located. When the template is deployed using the accompanying scripts, a private location in the subscription will be used and this value will be automatically generated.\n\nType: string\n\nPossible Values: null\n\nDefault: https://raw.githubusercontent.com/Azure/Moodle/master/\n\n\n### _artifactsLocationSasToken\n\nThe sasToken required to access _artifactsLocation.  When the template is deployed using the accompanying scripts, a sasToken will be automatically generated.\n\nType: securestring\n\nPossible Values: null\n\nDefault: \n\n\n### applyScriptsSwitch\n\nSwitch to process or bypass all scripts/extensions\n\nType: bool\n\nPossible Values: null\n\nDefault: true\n\n\n### azureBackupSwitch\n\nSwitch to configure AzureBackup and enlist VM's\n\nType: bool\n\nPossible Values: null\n\nDefault: false\n\n\n### redisDeploySwitch\n\nSwitch to deploy a redis cache or not. Note that certain versions of Moodle (e.g., 3.1) don't work well with Redis, so use this only for known well-working Moodle versions (e.g., 3.4).\n\nType: bool\n\nPossible Values: null\n\nDefault: false\n\n\n### vnetGwDeploySwitch\n\nSwitch to deploy a virtual network gateway or not\n\nType: bool\n\nPossible Values: null\n\nDefault: false\n\n\n### installObjectFsSwitch\n\nSwitch to install Moodle Object FS plugins (with Azure Blob storage)\n\nType: bool\n\nPossible Values: null\n\nDefault: false\n\n\n### installO365pluginsSwitch\n\nSwitch to install Moodle Office 365 plugins. As of May 22, 2018, O365 plugins for Moodle 3.5 haven't been released, so to set this true, you must set the moodleVersion to 3.4 or below.\n\nType: bool\n\nPossible Values: null\n\nDefault: false\n\n\n### installGdprPluginsSwitch\n\n(Should be used only for Moodle 3.4 & 3.3) Switch to install Moodle GDPR plugins. Note these require Moodle versions 3.4.2+ or 3.3.5+ and these are included by default in Moodle 3.5. So if you choose MOODLE_35_STABLE as your moodleVersion, do not set this to true.\n\nType: bool\n\nPossible Values: null\n\nDefault: false\n\n\n### htmlLocalCopySwitch\n\nSwitch to create a local copy of /moodle/html or not\n\nType: bool\n\nPossible Values: null\n\nDefault: true\n\n\n### ddosSwitch\n\nSwitch to create a DDoS protection plan\n\nType: bool\n\nPossible Values: null\n\nDefault: false\n\n\n### enableAccelNwForCtlrVmSwitch\n\nSwitch to enable Azure Accelerated Networking on the controller VM. Default to false because currently the default controller VM SKU (D1) doesn't support AN. Change this to true if you set the controller VM SKU to eligibible ones (e.g., D2) for better performance.\n\nType: bool\n\nPossible Values: null\n\nDefault: false\n\n\n### enableAccelNwForOtherVmsSwitch\n\nSwitch to enable Azure Accelerated Networking on all other VMs. Default to true because currently the default controller VM SKU for all other VMS (D2) does support AN. Change this to false if you set the SKU of any other VMs to an ineligibible one (e.g., D1) to avoid deployment failure.\n\nType: bool\n\nPossible Values: null\n\nDefault: true\n\n\n### httpsTermination\n\nIndicates where https termination occurs. 'VMSS' is for https termination at the VMSS instance VMs (using nginx https proxy). 'AppGw' is for https termination with an Azure Application Gateway. When selecting this, you need to specify all appGw* parameters. 'None' is for testing only with no https. 'None' may not be used with a separately configured https termination layer. If you want to use the 'None' option with your separately configured https termination layer, you'll need to update your Moodle config.php manually for $cfg->wwwroot and $cfg->sslproxy.\n\nType: string\n\nPossible Values: [\"VMSS\",\"AppGw\",\"None\"]\n\nDefault: VMSS\n\n\n### siteURL\n\nURL for Moodle site\n\nType: string\n\nPossible Values: null\n\nDefault: www.example.org\n\n\n### moodleVersion\n\nThe Moodle version you want to install.\n\nType: string\n\nPossible Values: [\"MOODLE_35_STABLE\",\"MOODLE_34_STABLE\",\"v3.4.3\",\"v3.4.2\",\"v3.4.1\",\"MOODLE_33_STABLE\",\"MOODLE_32_STABLE\",\"MOODLE_31_STABLE\",\"MOODLE_30_STABLE\",\"MOODLE_29_STABLE\"]\n\nDefault: MOODLE_35_STABLE\n\n\n### sshPublicKey\n\nssh public key\n\nType: string\n\nPossible Values: null\n\nDefault: null\n\n\n### sshUsername\n\nssh user name\n\nType: string\n\nPossible Values: null\n\nDefault: azureadmin\n\n\n### controllerVmSku\n\nVM size for the controller VM\n\nType: string\n\nPossible Values: null\n\nDefault: Standard_DS1_v2\n\n\n### webServerType\n\nWeb server type\n\nType: string\n\nPossible Values: [\"apache\",\"nginx\"]\n\nDefault: apache\n\n\n### autoscaleVmSku\n\nVM size for autoscaled web VMs\n\nType: string\n\nPossible Values: null\n\nDefault: Standard_DS2_v2\n\n\n### autoscaleVmCountMax\n\nMaximum number of autoscaled web VMs\n\nType: int\n\nPossible Values: null\n\nDefault: 10\n\n\n### autoscaleVmCountMin\n\nMinimum (also initial) number of autoscaled web VMs\n\nType: int\n\nPossible Values: null\n\nDefault: 1\n\n\n### osDiskStorageType\n\nAzure storage type for all VMs' OS disks. With htmlLocalCopySwith true, Premium_LRS (SSD) is strongly recommended, as PHP files will be served from OS disks.\n\nType: string\n\nPossible Values: [\"Premium_LRS\",\"Standard_LRS\"]\n\nDefault: Premium_LRS\n\n\n### dbServerType\n\nDatabase type\n\nType: string\n\nPossible Values: [\"postgres\",\"mysql\",\"mssql\"]\n\nDefault: mysql\n\n\n### dbLogin\n\nDatabase admin username\n\nType: string\n\nPossible Values: null\n\nDefault: dbadmin\n\n\n### mysqlPgresVcores\n\nMySql/Postgresql vCores. For Basic tier, only 1 & 2 are allowed. For GeneralPurpose tier, 2, 4, 8, 16, 32 are allowed. For MemoryOptimized, 2, 4, 8, 16 are allowed.\n\nType: int\n\nPossible Values: [1,2,4,8,16,32]\n\nDefault: 2\n\n\n### mysqlPgresStgSizeGB\n\nMySql/Postgresql storage size in GB. Minimum 5GB, increase by 1GB, up to 1TB (1024 GB)\n\nType: int\n\nPossible Values: null\n\nDefault: 125\n\n\n### mysqlPgresSkuTier\n\nMySql/Postgresql sku tier\n\nType: string\n\nPossible Values: [\"Basic\",\"GeneralPurpose\",\"MemoryOptimized\"]\n\nDefault: GeneralPurpose\n\n\n### mysqlPgresSkuHwFamily\n\nMySql/Postgresql sku hardware family. Central US is Gen4 only, so make sure to change this parameter to Gen4 if your deployment is on Central US.\n\nType: string\n\nPossible Values: [\"Gen4\",\"Gen5\"]\n\nDefault: Gen5\n\n\n### mysqlVersion\n\nMysql version\n\nType: string\n\nPossible Values: [\"5.6\",\"5.7\"]\n\nDefault: 5.7\n\n\n### postgresVersion\n\nPostgresql version\n\nType: string\n\nPossible Values: [\"9.5\",\"9.6\"]\n\nDefault: 9.6\n\n\n### sslEnforcement\n\nMySql/Postgresql SSL connection\n\nType: string\n\nPossible Values: [\"Disabled\",\"Enabled\"]\n\nDefault: Disabled\n\n\n### mssqlDbServiceObjectiveName\n\nMS SQL database service object names\n\nType: string\n\nPossible Values: [\"S1\",\"S2\",\"S3\",\"S4\",\"S5\",\"S6\",\"S7\",\"S9\"]\n\nDefault: S1\n\n\n### mssqlDbSize\n\nMS SQL database size\n\nType: string\n\nPossible Values: [\"100MB\",\"250MB\",\"500MB\",\"1GB\",\"2GB\",\"5GB\",\"10GB\",\"20GB\",\"30GB\",\"40GB\",\"50GB\",\"100GB\",\"250GB\",\"300GB\",\"400GB\",\"500GB\",\"750GB\",\"1024GB\"]\n\nDefault: 250GB\n\n\n### mssqlDbEdition\n\nMS SQL DB edition\n\nType: string\n\nPossible Values: [\"Basic\",\"Standard\"]\n\nDefault: Standard\n\n\n### mssqlVersion\n\nMssql version\n\nType: string\n\nPossible Values: [\"12.0\"]\n\nDefault: 12.0\n\n\n### fileServerType\n\nFile server type: GlusterFS, NFS, and NFS-HA (2-VM highly available NFS cluster)\n\nType: string\n\nPossible Values: [\"gluster\",\"nfs\",\"nfs-ha\",\"nfs-byo\"]\n\nDefault: nfs\n\n\n### nfsByoIpExportPath\n\nIP address and export path of the BYO-NFS share when fileServerType == nfs-byo. E.g., 172.16.1.8:/msazure\n\nType: string\n\nPossible Values: null\n\nDefault: \n\n\n### fileServerDiskSize\n\nSize per disk for gluster nodes or nfs server\n\nType: int\n\nPossible Values: null\n\nDefault: 127\n\n\n### fileServerDiskCount\n\nNumber of disks in raid0 per gluster node or nfs server\n\nType: int\n\nPossible Values: null\n\nDefault: 4\n\n\n### fileServerVmSku\n\nVM size for the gluster or NFS-HA nodes\n\nType: string\n\nPossible Values: null\n\nDefault: Standard_DS2_v2\n\n\n### keyVaultResourceId\n\n(VMSS https termination only) Azure Resource Manager resource ID of the Key Vault in case you stored your SSL cert in an Azure Key Vault (Note that this Key Vault must have been pre-created on the same Azure region where this template is being deployed). Leave this blank if you didn't. Resource ID example: /subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/xxx/providers/Microsoft.KeyVault/vaults/yyy. This value can be obtained from keyvault.sh output if you used the script to store your SSL cert in your Key Vault.\n\nType: string\n\nPossible Values: null\n\nDefault: \n\n\n### sslCertKeyVaultURL\n\n(VMSS https termination only) Azure Key Vault URL for your stored SSL cert. This value can be obtained from keyvault.sh output if you used the script to store your SSL cert in your Key Vault. This parameter is ignored if the keyVaultResourceId parameter is blank.\n\nType: string\n\nPossible Values: null\n\nDefault: \n\n\n### sslCertThumbprint\n\n(VMSS https termination only) Thumbprint of your stored SSL cert. This value can be obtained from keyvault.sh output if you used the script to store your SSL cert in your Key Vault. This parameter is ignored if the keyVaultResourceId parameter is blank.\n\nType: string\n\nPossible Values: null\n\nDefault: \n\n\n### caCertKeyVaultURL\n\n(VMSS https termination only) Azure Key Vault URL for your stored CA (Certificate Authority) cert. This value can be obtained from keyvault.sh output if you used the script to store your CA cert in your Key Vault. This parameter is ignored if the keyVaultResourceId parameter is blank.\n\nType: string\n\nPossible Values: null\n\nDefault: \n\n\n### caCertThumbprint\n\n(VMSS https termination only) Thumbprint of your stored CA cert. This value can be obtained from keyvault.sh output if you used the script to store your CA cert in your Key Vault. This parameter is ignored if the keyVaultResourceId parameter is blank.\n\nType: string\n\nPossible Values: null\n\nDefault: \n\n\n### appGwSslCertKeyVaultResourceId\n\n(App Gateway https termination only) Azure Key Vault URL for your stored SSL cert, again for App Gateway https termination case only. (Note that this Key Vault must have been pre-created on the same Azure region where this template is being deployed). Leave this blank if you didn't. Resource ID example: /subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/xxx/providers/Microsoft.KeyVault/vaults/yyy.\n\nType: string\n\nPossible Values: null\n\nDefault: \n\n\n### appGwSslCertKeyVaultSecretName\n\n(App Gateway https termination only) Name of the Azure Key Vault secret that's stored in the previously specified Key Vault as a PFX certificate (with no password) for your site's SSL cert. This secret must be pre-populated in the specified Key Vault with the matching name.\n\nType: string\n\nPossible Values: null\n\nDefault: \n\n\n### appGwSkuName\n\n(App Gateway https termination only) Name of the Applicate Gateway SKU\n\nType: string\n\nPossible Values: [\"Standard_Small\",\"Standard_Medium\",\"Standard_Large\",\"WAF_Medium\",\"WAF_Large\"]\n\nDefault: Standard_Medium\n\n\n### appGwSkuTier\n\n(App Gateway https termination only) Tier of the Applicate Gateway\n\nType: string\n\nPossible Values: [\"Standard\",\"WAF\"]\n\nDefault: Standard\n\n\n### appGwSkuCapacity\n\n(App Gateway https termination only) Capacity instance count) of the Applicate Gateway\n\nType: int\n\nPossible Values: null\n\nDefault: 2\n\n\n### storageAccountType\n\nStorage Account type. This storage account is only for the Moodle ObjectFS plugin and/or the (currently disabled) Azure Files file share option\n\nType: string\n\nPossible Values: [\"Standard_LRS\",\"Standard_GRS\",\"Standard_ZRS\"]\n\nDefault: Standard_LRS\n\n\n### searchType\n\noptions of moodle global search\n\nType: string\n\nPossible Values: [\"none\",\"azure\",\"elastic\"]\n\nDefault: none\n\n\n### tikaService\n\noptions of enabling tika service for file searching in moodle\n\nType: string\n\nPossible Values: [\"none\",\"tika\"]\n\nDefault: none\n\n\n### azureSearchSku\n\nthe search service level you want to create.\n\nType: string\n\nPossible Values: [\"free\",\"basic\",\"standard\",\"standard2\",\"standard3\"]\n\nDefault: basic\n\n\n### azureSearchReplicaCount\n\nReplicas distribute search workloads across the service. You need 2 or more to support high availability (applies to Basic and Standard only).\n\nType: int\n\nPossible Values: null\n\nDefault: 3\n\n\n### azureSearchPartitionCount\n\nPartitions allow for scaling of document count as well as faster indexing by sharding your index over multiple Azure Search units.\n\nType: int\n\nPossible Values: [1,2,3,4,6,12]\n\nDefault: 1\n\n\n### azureSearchHostingMode\n\nApplicable only for azureSearchSku set to standard3. You can set this property to enable a single, high density partition that allows up to 1000 indexes, which is much higher than the maximum indexes allowed for any other azureSearchSku.\n\nType: string\n\nPossible Values: [\"default\",\"highDensity\"]\n\nDefault: default\n\n\n### elasticVmSku\n\nVM size for the elastic search nodes\n\nType: string\n\nPossible Values: null\n\nDefault: Standard_DS2_v2\n\n\n### tikaVmSku\n\nVM size for the tika search nodes\n\nType: string\n\nPossible Values: null\n\nDefault: Standard_DS2_v2\n\n\n### customVnetId\n\nAzure Resource ID of the Azure virtual network where you want to deploy your Moodle resources. A vnet resource ID is of the following format: /subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxxxxxx/resourceGroups/gggg/providers/Microsoft.Network/virtualNetworks/vvvv. Note that this virtual network must be on the same Azure location as this template deployment location. If this parameter is blank, a new Azure virtual network will be created and used. In that case, the address space of the newly created virtual network will be */16 of the following vNetAddressSpace parameter value below.\n\nType: string\n\nPossible Values: null\n\nDefault: \n\n\n### vNetAddressSpace\n\nAddress range for the Moodle virtual network and various subnets - presumed /16 for a newly created vnet in case customVnetId is blank. Further subneting (a number of */24 subnets starting from the xxx.yyy.zzz.0/24 will be created on a newly created vnet or your BYO-vnet (specified in customVnetId parameter).\n\nType: string\n\nPossible Values: null\n\nDefault: 172.31.0.0\n\n\n### gatewayType\n\nVirtual network gateway type\n\nType: string\n\nPossible Values: [\"Vpn\",\"ER\"]\n\nDefault: Vpn\n\n\n### vpnType\n\nVirtual network gateway vpn type\n\nType: string\n\nPossible Values: [\"RouteBased\",\"PolicyBased\"]\n\nDefault: RouteBased\n\n\n### loadBalancerSku\n\nLoadbalancer SKU\n\nType: string\n\nPossible Values: [\"Basic\",\"Standard\"]\n\nDefault: Basic\n\n\n### location\n\nAzure Location for all resources.\n\nType: string\n\nPossible Values: null\n\nDefault: [resourceGroup().location]\n\n\n"
  },
  {
    "path": "docs/Preparation.md",
    "content": "# Environment Preparation\n\nThis document describes how to ensure your environment is configured\nfor working with Moodle on Azure.\n\n## Prerequisites\n\nIn order to configure our deployment and tools we'll set up some\n[environment variables](./Environment-Variables.md) to ensure consistency.\n\n## Required software\n\nWe'll use a number of tools when working with Moodle on Azure. Let's\nensure they are all installed:\n\n``` shell\nsudo apt-get update\nsudo apt-get install wget -y\nsudo apt-get openssh-client -y\n```\n\nThe [Azure CLI](https://docs.microsoft.com/en-us/cli/azure/install-azure-cli-apt?view=azure-cli-latest) is also important:\n\n```bash\nAZ_REPO=$(lsb_release -cs)\necho \"deb [arch=amd64] https://packages.microsoft.com/repos/azure-cli/ $AZ_REPO main\" | sudo tee /etc/apt/sources.list.d/azure-cli.list\nsudo apt-key adv --keyserver packages.microsoft.com --recv-keys 52E16F86FEE04B979B07E28DB02C46DF417A0893\nsudo apt-get install apt-transport-https\nsudo apt-get update && sudo apt-get install azure-cli\n```\n\n## Ensure we have a valid SSH key pair\n\nWe use SSH for secure communication with our hosts. The following line\nwill check there is a valid SSH key available and, if not, create one.\n\n```\nif [ ! -f \"$MOODLE_SSH_KEY_FILENAME\" ]; then ssh-keygen -t rsa -N \"\" -f $MOODLE_SSH_KEY_FILENAME; fi\n```\n## Checkout the Moodle ARM Template\n\nThe Moodle Azure Resource Manager template is hosted on GitHub. We'll\ncheckout the template into our workspace.\n\n```\ngit clone git@github.com:Azure/Moodle.git $MOODLE_AZURE_WORKSPACE/arm_template\n```\n\n# Validation\n\nAfter completing these steps we should have, amonst other things, a\ncomplete checkout of the Moodle templates from GitHub:\n\n``` bash\nls $MOODLE_AZURE_WORKSPACE/arm_template\n```\n\nResults:\n\n``` expected_similarity=0.4\nazuredeploy.json  azuredeploy.parameters.json  CONTRIBUTE.md  docs  env.json  etc  images  LICENSE  LICENSE-DOCS  metadata.json  nested\nREADME.md\n```\n\nWe should also have a number of applications installed, such as the Azure CLI:\n\n``` bash\nif hash az 2>/dev/null; then echo \"Azure CLI Installed\"; else echo \"Missing dependency: Azure CLI\"; fi\n```\n\n```\nAzureCLI Installed\n```\n"
  },
  {
    "path": "docs/SslCert.md",
    "content": "# SSL Certificate Management\n\nA valid SSL (TLS) certificate should be used with your domain name for the Moodle\nsite to be deployed using the templates. By default, the templates will configure\nthe HTTPS server with a self-signed SSL server certificate/private key, which can\nbe manually changed with your own valid SSL server certificate/private key after\nthe deployment.\n\nIf you'd like to configure the Moodle cluster (to be deployed) with your own domain\nand your valid SSL server certificate/private key, then you can do so by utilizing\n[Azure Key Vault](https://azure.microsoft.com/en-us/services/key-vault/) and\nconfiguring the related template parameters as described below. This support is\nbased on [another similar work](https://github.com/Azure/azure-quickstart-templates/tree/master/201-vmss-ubuntu-web-ssl)\nand adapted to our situation.\n\n## Initial deployment\n\nTo configure the Moodle cluster (to be deployed) with your purchased SSL certificate,\ncurrently the related files should be stored in an Azure Key Vault as secrets, so that\nAzure Resource Manager can reference when it deploys VMs as specified in templates.\n\nYou can create your own Azure Key Vault and store your purchased SSL certificate (called\n'import' in Azure Key Vault terminology) by following related documentation like\n[this](https://docs.microsoft.com/en-us/azure/key-vault/key-vault-manage-with-cli2).\nHowever, the related files must be stored in a specific format, so we created a\nshell script (`keyvault.sh`) that will perform all necessary steps for this purpose.\nTo use this script, you'll first need to upload your SSL certificate/private key files\n(.pem) to your deployment environment you set up by following the [preparation document](Preparation.md)\n(a Linux command line). The .pem files should be as follows:\n\n- `cert.pem`: The SSL certificate only in PEM format\n- `key.pem`: The private key for the SSL certificate only in PEM format\n- `chain.pem`: This is optional in case your server certificate is signed by an intermediate CA (Certificate Authority) certificate, instead of a root CA certificate. Currently only one intermediate CA certificate is supported by the script.\n\nOnce you updloaded the files to your deployment environment, you can run the following command\nto create an Azure Key Vault on your subscription and store your SSL certificate, private key, and optionally\nthe intermediate CA certificate:\n\n``` bash\n$ bash $MOODLE_AZURE_WORKSPACE/arm_template/etc/keyvault.sh <key_vault_name> <resource_group_name> <azure_region> <secret_name> cert.pem key.pem chain.pem\n```\n\nMake sure to set `<azure_region>` the same as the Azure region you'll be using to deploy the Moodle template.\nAssign desired names for `<key_vault_name>`, `<resource_group_name>` (you can use an existing resource group) and `<secret_name>`.\n`<secret_name>` is not very important in our deployment. Then you'll get outputs as follows:\n\n```\n...\nSpecified SSL cert/key .pem files are now stored in your Azure Key Vault and ready to be used by the template.\nUse the following values for the related template parameters:\n\n- keyVaultResourceId: /subscriptions/206c66fc-a48c-480d-ad06-0d429e82c586/resourceGroups/keyvault/providers/Microsoft.KeyVault/vaults/mdl-kv\n- sslCertKeyVaultURL: https://mdl-kv.vault.azure.net/secrets/mymoodlesitecert/4c88452fe72b4d469253af48348f4944\n- sslCertThumbprint:  56478E4F9555662476E2763D909F50B3DD26FF84\n- caCertKeyVaultURL:  https://mdl-kv.vault.azure.net/secrets/camymoodlesitecert/684efab1f2124e71a2c809457d10808b\n- caCertThumbprint:   E6A3B45B062D509B3382282D196EFE97D5956CCB\nDone\n```\n\nThis example outputs assumes `\"keyvault\"` is used for `<resource_group_name>`, `\"mdl-kv\"` for `<key_vault_name>`,\nand `\"mymoodlesitecert\"` for `<secret_name>`. Note that `caCertKeyVaultURL` and `caCertThumbprint` will be empty\nif you didn't specify `chain.pem`. Then you can copy these outputs to the template's corresponding parameters,\nand Azure Resource Manager will install the certificate and the private key on the deployed VMs and the deployed\nHTTPS server will use this certificate and private key.\n\n## Certificate rotation\n\nAnother important benefit of using Azure Key Vault is to handle certificate expiration/rotation automatically.\nUnfortunately, the current implementation doesn't support the auto-rotation. So when it becomes near your SSL\ncertificate's expiry, you'll need to manually update the deployed certificate and private key files\n(it's in `/moodle/certs/nginx.{crt,key}` on the controller VM) and restart all the web frontend VM instances.\nWe'll improve our implementation to support auto-rotation in the future."
  },
  {
    "path": "docs/Test.md",
    "content": "# Test a Moodle Instance\n\n## Prerequisites\n\nIt is obviously necessary to have a [Moodle cluster up and running](./Deploy.md).\n\n## Next Steps\n\n  * [Delete all Resources](./Delete.md)\n"
  },
  {
    "path": "docs/env.json",
    "content": "{\n    \"MOODLE_RG_NAME\": \"rgmoodlearm13tagging\",\n    \"MOODLE_RG_LOCATION\": \"canadacentral\",\n    \"MOODLE_DEPLOYMENT_NAME\": \"MainDeployment\"\n}\n"
  },
  {
    "path": "env.json",
    "content": "{\n    \"MOODLE_RG_NAME\": \"rgmoodlearm12\",\n    \"MOODLE_RG_LOCATION\": \"canadacentral\",\n    \"MOODLE_DEPLOYMENT_NAME\": \"MainDeployment\"\n}\n"
  },
  {
    "path": "etc/changeBranchInURL.sh",
    "content": "#!/bin/bash\n\nfrom=$1\nto=$2\n\nsed -i s/%2F${from}%2F/%2F${to}%2F/g README.md\nsed -i s#/${from}/#/${to}/#g azuredeploy.json\n"
  },
  {
    "path": "etc/checkBaseUrls.sh",
    "content": "# Ensure that the Base URL for templates, scripts and deploy to Azure buttons\n# is correctly set for the current git branch.\n\n# Correct values for locations\nCURRENT_BRANCH=$(git branch | sed -n -e 's/^\\* \\(.*\\)/\\1/p')\necho \"Current git branch is '$CURRENT_BRANCH'\"\n\nBASE_TEMPLATE_URL=https://raw.githubusercontent.com/Azure/Moodle/$CURRENT_BRANCH/nested/\necho \"Base template URL: $BASE_TEMPLATE_URL\"\n\nSCRIPT_LOCATION=https://raw.githubusercontent.com/Azure/Moodle/$CURRENT_BRANCH/scripts/\necho \"Script location: $SCRIPT_LOCATION\"\n\nDEPLOY_TO_AZURE_URL=https://portal.azure.com/#create/Microsoft.Template/uri/https%3A%2F%2Fraw.githubusercontent.com%2FAzure%2FMoodle%2F$CURRENT_BRANCH%2Fazuredeploy.json\necho \"Deploy to Azure URL: $DEPLOY_TO_AZURE_URL\"\n\nVISUALIZE_URL=http://armviz.io/#/?load=https%3A%2F%2Fraw.githubusercontent.com%2FAzure%2FMoodle%2F$CURRENT_BRANCH%2Fazuredeploy.json\necho \"Visualize template URL: $VISUALIZE_URL\"\n\n# Check values in README.md\n\nVALUE=$(sed -n -e 's/.*deploybutton.png)](\\([^)]*\\)).*/\\1/p' README.md)\nif [[ \"$VALUE\" = \"$DEPLOY_TO_AZURE_URL\" ]]\nthen\n    echo \"Deploy to Azure URL is set correctly\"\nelse\n    echo \"!!!!! Deploy to Azure URL is not set correctly in README.md, it is currently:\"\n    echo $VALUE\nfi\n\nVALUE=$(sed -n -e 's/.*visualizebutton.png)](\\([^)]*\\)).*/\\1/p' README.md)\nif [[ \"$VALUE\" = \"$VISUALIZE_URL\" ]]\nthen\n    echo \"Visualize URL is set correctly\"\nelse\n    echo \"!!!!! Visualize URL is not set correctly in README.md, it is currently:\"\n    echo $VALUE\nfi\n\n# Check values in azuredeploy.json\n\nVALUE=$(sed -n -e 's/.*\\\"baseTemplateUrl\\\": \\\"\\([^\\\"]*\\)\\\",/\\1/p' azuredeploy.json)\nif [[ \"$VALUE\" = \"$BASE_TEMPLATE_URL\" ]]\nthen\n    echo \"baseTemplateURL is set correctly\"\nelse\n    echo \"!!!!! baseTemplateURL is not set correctly, it is currently:\"\n    echo $VALUE\nfi\n\nVALUE=$(sed -n -e 's/.*\\\"scriptLocation\\\": \\\"\\([^\\\"]*\\)\\\",/\\1/p' azuredeploy.json)\nif [[ \"$VALUE\" = \"$SCRIPT_LOCATION\" ]]\nthen\n    echo \"scriptLocation is set correctly\"\nelse\n    echo \"!!!!! scriptLocation is not set correctly, it is currently:\"\n    echo $VALUE\nfi\n"
  },
  {
    "path": "etc/keyvault.sh",
    "content": "#!/bin/bash\n\n# Based on https://github.com/Azure/azure-quickstart-templates/blob/master/201-vmss-ubuntu-web-ssl/keyvault.sh\n\n#set -e\n\nusage()\n{\n    echo usage: keyvault.sh '<keyvaultname> <resource group name> <location> <secretname> <certpemfile> <keypemfile> <cacertpemfile>'\n    echo The cacertpem file is optional. The template will accept a self-signed cert and key.\n}\n\ncreategroup()\n{\n\n    local group=$(az group show -g $rgname)  \n    if [ -n \"$group\" ]; then    \n        echo Resource Group $rgname already exists. Skipping creation.\n    else\n        # Create a resource group for the keyvault\n        az group create -n $rgname -l $location\n    fi\n}\n\ncreatekeyvault()\n{\n\n    az keyvault show -n $vaultname 2> /dev/null\n    if [ $? -eq 0 ]\n    then    \n        echo Key Vault $vaultname already exists. Skipping creation.\n    else   \n        echo Creating Key Vault $vaultname.\n\n        creategroup \n        # Create the key vault\n        az keyvault create --name $vaultname --resource-group $rgname --location $location --enabled-for-template-deployment true --enabled-for-deployment true\n    fi  \n}\n\nconvertcert()\n{\n    local cert=$1\n    local key=$2\n    local pfxfile=$3\n    local pass=$4\n\n    echo Creating PFX $pfxfile\n    openssl pkcs12 -export -out $pfxfile -inkey $key -in $cert -password pass:$pass 2> /dev/null\n    if [ $? -eq 1 ]\n    then\n        echo problem converting $key and $cert to pfx\n        exit 1\n    fi    \n\n    fingerprint=$(openssl x509 -in $cert -noout -fingerprint | cut -d= -f2 | sed 's/://g' )\n}\n\nconvertcacert()\n{\n    local cert=$1\n    local pfxfile=$2\n    local pass=$3\n\n    echo Creating PFX $pfxfile\n    openssl pkcs12 -export -out $pfxfile -nokeys -in $cert -password pass:$pass 2> /dev/null\n    if [ $? -eq 1 ]\n    then\n        echo problem converting $cert to pfx\n        exit 1\n    fi    \n\n    fingerprint=$(openssl x509 -in $cert -noout -fingerprint | cut -d= -f2 | sed 's/://g' )\n}\n\nstoresecret()\n{\n    local secretfile=$1\n    local name=$2\n    filecontentencoded=$( cat $secretfile | base64 $base64_unwrap )\n\njson=$(cat << EOF\n{\n\"data\": \"${filecontentencoded}\",\n\"dataType\" :\"pfx\",\n\"password\": \"${pwd}\"\n}\nEOF\n)\n\n    jsonEncoded=$( echo $json | base64 $base64_unwrap )\n\n    r=$(az keyvault secret set --vault-name $vaultname --name $name --value $jsonEncoded)\n    if [ $? -eq 1 ]\n    then\n        echo problem storing secret $name in $vaultname \n        exit 1\n    fi    \n\n    id=$(az keyvault secret show --vault-name $vaultname --name $name --query id -o tsv)\n    echo Secret ID is $id\n}\n\n# We need at least 6 parameters\nif [ \"$#\" -lt 6 ]; then\n    usage\n    exit\nfi\n\n# The base64 command on OSX does not know about the -w parameter, but outputs unwrapped base64 by default\nbase64_unwrap=\"-w 0\"\n[[ $(uname) == \"Darwin\" ]] && base64_unwrap=\"\"\n\nvaultname=$1\nrgname=$2\nlocation=$3\nsecretname=$4\ncertfile=$5\nkeyfile=$6\ncacertfile=$7\n\n# Create a random password with 33 bytes of entropy\n# I picked 33 so the last character will not be =\npwd=$(dd if=/dev/urandom bs=32 count=1 2>/dev/null | base64)\n\ncertpfxfile=${certfile%.*crt}.pfx\ncacertpfxfile=${cacertfile%.*crt}.pfx\ncasecretname=ca$secretname\n\ncreatekeyvault\n\n# converting SSL cert to pfx\nconvertcert $certfile $keyfile $certpfxfile $pwd\ncertprint=$fingerprint\necho $certpfxfile fingerprint is $fingerprint\n# storing pfx in keyvault\necho Storing $certpfxfile as $secretname\nstoresecret $certpfxfile $secretname\ncertid=$id   \nrm -f $certpfxfile\n\nif [ ! -z $cacertfile ]\nthen\n    # converting CA cert to pfx\n    convertcacert $cacertfile $cacertpfxfile $pwd\n    echo $cacertpfxfile fingerprint is $fingerprint\n    cacertprint=$fingerprint\n    # storing pfx in key vault\n    echo Storing $cacertpfxfile as $casecretname\n    storesecret $cacertpfxfile $casecretname   \n    cacertid=$id\n    rm -f $cacertpfxfile\nfi\n\necho \"Specified SSL cert/key .pem files are now stored in your Azure Key Vault and ready to be used by the template.\"\necho \"Use the following values for the related template parameters:\"\necho\necho \"- keyVaultResourceId: $(az keyvault show --name $vaultname --query id -o tsv)\"\necho \"- sslCertKeyVaultURL: $certid\"\necho \"- sslCertThumbprint:  $certprint\"\necho \"- caCertKeyVaultURL:  $cacertid\"\necho \"- caCertThumbprint:   $cacertprint\"\necho Done\n"
  },
  {
    "path": "etc/travis/Configuration.py",
    "content": "import json\nimport os\nimport time\n\nfrom azure.mgmt.resource.resources.v2017_05_10.models import DeploymentMode\n\n\nclass Configuration:\n    def __init__(self):\n        self.deployment_name = 'azuredeploy'\n        self.client_id = os.getenv('SPNAME')\n        self.secret = os.getenv('SPPASSWORD')\n        self.tenant_id = os.getenv('SPTENANT')\n        self.location = os.getenv('LOCATION', 'westus2')\n        self.source_branch = self.identify_source_branch()\n        self.fullci_branches = os.getenv('FULLCI_BRANCHES', 'master').split(':')\n        self.commit_message = os.getenv('TRAVIS_COMMIT_MESSAGE', None)\n        self.ssh_key = self.identify_ssh_key()\n        self.resource_group = self.identify_resource_group()\n        self.deployment_properties = self.generate_deployment_properties()\n\n    def identify_resource_group(self):\n        resource_group = os.getenv('RESOURCEGROUP')\n        if resource_group is None:\n            resource_group = 'azmdl-travis-' + os.getenv('TRAVIS_BUILD_NUMBER', 'manual-{}'.format(time.time()))\n        return resource_group\n\n    def identify_ssh_key(self):\n        ssh_key = os.getenv('SPSSHKEY')\n        if ssh_key is None:\n            with open('azure_moodle_id_rsa.pub', 'r') as sshkey_fd:\n                ssh_key = sshkey_fd.read()\n        return ssh_key\n\n    def generate_deployment_properties(self):\n        with open('azuredeploy.json', 'r') as template_fd:\n            template = json.load(template_fd)\n\n        with open('azuredeploy.parameters.json', 'r') as parameters_fd:\n            parameters = json.load(parameters_fd)\n        parameters = parameters['parameters']\n        parameters['sshPublicKey']['value'] = self.ssh_key\n        parameters['_artifactsLocation'] = {'value': self.identify_artifacts_location()}\n\n        return {\n            'mode': DeploymentMode.incremental,\n            'template': template,\n            'parameters': parameters,\n        }\n\n    def identify_artifacts_location(self):\n        slug = os.getenv('TRAVIS_PULL_REQUEST_SLUG')\n        if not slug:\n            slug = os.getenv('TRAVIS_REPO_SLUG')\n        return \"https://raw.githubusercontent.com/{}/{}/\".format(slug, self.source_branch)\n\n    def identify_source_branch(self):\n        branch = os.getenv('TRAVIS_PULL_REQUEST_BRANCH')\n        if not branch:\n            branch = os.getenv('TRAVIS_BRANCH')\n        return branch\n\n    def is_valid(self):\n        valid = True\n\n        for key, value in vars(self).items():\n            if value is None:\n                valid = False\n                print('(missing configuration for {})'.format(key))\n\n        if self.deployment_properties['parameters']['_artifactsLocation']['value'] is None:\n            valid = False\n            print('(could not identify _artifactsLocation)')\n\n        return valid\n\n    def should_run_full_ci(self):\n        if self.source_branch in self.fullci_branches:\n            return True\n\n        message = self.commit_message.upper()\n        if '[FULL CI]' in message or '[FULLCI]' in message:\n            return True\n\n        return False\n"
  },
  {
    "path": "etc/travis/DeploymentTester.py",
    "content": "import os\nimport pycurl\nimport sys\nimport tempfile\nimport time\nimport urllib\nfrom io import BytesIO\nfrom pycurl import Curl\n\nfrom azure.mgmt.resource import ResourceManagementClient\nfrom azure.mgmt.subscription import SubscriptionClient\nfrom msrestazure.azure_active_directory import ServicePrincipalCredentials\n\nfrom travis.Configuration import Configuration\n\n\nclass DeploymentTester:\n    @staticmethod\n    def elapsed(since):\n        elapsed = int(time.time() - since)\n        elapsed = '{:02d}:{:02d}:{:02d}'.format(elapsed // 3600, (elapsed % 3600 // 60), elapsed % 60)\n        return elapsed\n\n    def __init__(self):\n        self.config = Configuration()\n        self.deployment = None\n\n        self.credentials = None\n        \"\"\":type : ServicePrincipalCredentials\"\"\"\n\n        self.resource_client = None\n        \"\"\":type : ResourceManagementClient\"\"\"\n\n    def run(self):\n        should_delete_resource_group = False\n        try:\n            self.check_configuration()\n            self.login()\n            self.create_resource_group()\n            self.validate()\n            if not self.config.should_run_full_ci():\n                print('\\n\\nBasic CI tests successful.')\n                should_delete_resource_group = True\n                return\n            self.deploy()\n            self.moodle_smoke_test()\n            self.moodle_admin_login()\n            print('\\n\\nFull CI tests successful!')\n            should_delete_resource_group = True\n        finally:\n            if should_delete_resource_group:\n                self.delete_resource_group()\n\n    def check_configuration(self):\n        print('\\nChecking configuration...')\n        if not self.config.is_valid():\n            print('No Azure deployment info given, skipping test deployment and exiting.')\n            print('Further information: https://github.com/Azure/Moodle#automated-testing-travis-ci')\n            sys.exit()\n        artifacts_location = self.config.deployment_properties['parameters']['_artifactsLocation']\n        print('- Detected \"_artifactsLocation\": ' + artifacts_location['value'])\n        print(\"(all check)\")\n\n    def login(self):\n        print('\\nLogging in...')\n        self.credentials = ServicePrincipalCredentials(\n            client_id=self.config.client_id,\n            secret=self.config.secret,\n            tenant=self.config.tenant_id,\n        )\n        print('(got credentials)')\n        subscription_client = SubscriptionClient(self.credentials)\n        subscription = next(subscription_client.subscriptions.list())\n        print('(found subscription)')\n        self.resource_client = ResourceManagementClient(self.credentials, subscription.subscription_id)\n        print(\"(logged in)\")\n\n    def create_resource_group(self):\n        print('\\nCreating group \"{}\" on \"{}\"...'.format(self.config.resource_group, self.config.location))\n        self.resource_client.resource_groups.create_or_update(self.config.resource_group,\n                                                              {'location': self.config.location})\n        print('(created)')\n\n    def validate(self):\n        print('\\nValidating template...')\n\n        validation = self.resource_client.deployments.validate(self.config.resource_group,\n                                                               self.config.deployment_name,\n                                                               self.config.deployment_properties)\n        if validation.error is not None:\n            print(\"*** VALIDATION FAILED ({}) ***\".format(validation.error))\n            print(validation.error.message)\n            for detail in validation.error.details:\n                print(\"- {}:\\n{}\".format(detail.code, detail.message))\n            sys.exit(1)\n\n        print(\"(valid)\")\n\n    def deploy(self):\n        print('\\nDeploying template, feel free to take a nap...')\n        deployment = self.resource_client.deployments.create_or_update(self.config.resource_group,\n                                                                       self.config.deployment_name,\n                                                                       self.config.deployment_properties)\n        \"\"\":type : msrestazure.azure_operation.AzureOperationPoller\"\"\"\n        started = time.time()\n        while not deployment.done():\n            print('... after {} still \"{}\" ...'.format(self.elapsed(started), deployment.status()))\n            deployment.wait(60)\n        print(\"WAKE UP! After {} we finally got status {}.\".format(self.elapsed(started), deployment.status()))\n\n        print(\"Checking deployment response...\")\n        properties = deployment.result(0).properties\n        if properties.provisioning_state != 'Succeeded':\n            print(\"*** DEPLOY FAILED ***\")\n            print('Provisioning state: ' + properties.provisioning_state)\n            sys.exit(1)\n        self.load_deployment_outputs(properties.outputs)\n        print(\"(success)\")\n\n    def load_deployment_outputs(self, outputs):\n        self.deployment = {}\n        for key, value in outputs.items():\n            self.deployment[key] = value['value']\n            print(\"- Found: \" + key)\n\n    def moodle_smoke_test(self):\n        print(\"\\nMoodle Smoke Test...\")\n        url = 'https://' + self.deployment['siteURL']\n        curl = Curl()\n        curl.setopt(pycurl.URL, url)\n        curl.setopt(pycurl.SSL_VERIFYPEER, False)\n        curl.setopt(pycurl.WRITEFUNCTION, lambda x: None)\n        curl.perform()\n        status = curl.getinfo(pycurl.HTTP_CODE)\n        if status != 200:\n            print(\"*** DEPLOY FAILED ***\")\n            print('HTTP Status Code: {}'.format(status))\n            sys.exit(1)\n        print('(ok: {})'.format(status))\n\n    def moodle_admin_login(self):\n        print(\"\\nLogging in into Moodle as 'admin'...\")\n        response = self.moodle_admin_login_curl()\n        if 'Admin User' not in response:\n            print(\"*** FAILED: 'Admin User' keyword not found ***\")\n            sys.exit(1)\n        print('(it worked)')\n\n    def moodle_admin_login_curl(self):\n        fd, path = tempfile.mkstemp()\n        try:\n            response = BytesIO()\n            url = 'https://' + self.deployment['siteURL'] + '/login/index.php'\n            curl = Curl()\n            curl.setopt(pycurl.URL, url)\n            curl.setopt(pycurl.SSL_VERIFYPEER, False)\n            curl.setopt(pycurl.WRITEFUNCTION, response.write)\n            curl.setopt(pycurl.POST, True)\n            curl.setopt(pycurl.COOKIEJAR, path)\n            curl.setopt(pycurl.COOKIEFILE, path)\n            post = urllib.parse.urlencode({'username': 'admin', 'password': self.deployment['moodleAdminPassword']})\n            curl.setopt(pycurl.POSTFIELDS, post)\n            curl.setopt(pycurl.FOLLOWLOCATION, True)\n            curl.perform()\n            status = curl.getinfo(pycurl.HTTP_CODE)\n            if status != 200:\n                print(\"*** FAILED: {} ***\".format(status))\n                sys.exit(1)\n            response = response.getvalue().decode('utf-8')\n        finally:\n            os.remove(path)\n        return response\n\n    def delete_resource_group(self):\n        print('\\n\\nDeleting the resource group for this passing build...')\n        self.resource_client.resource_groups.delete(self.config.resource_group, polling=False)\n        print('(delete initiated, not polling)')\n"
  },
  {
    "path": "etc/travis/__init__.py",
    "content": ""
  },
  {
    "path": "etc/travis.py",
    "content": "#!/usr/bin/env python3\n\nfrom travis.DeploymentTester import DeploymentTester\n\nDeploymentTester().run()\n"
  },
  {
    "path": "etc/updateDocsParametersMd.sh",
    "content": "#!/bin/bash\n\n# Should be run at the git repo root as: $ ./etc/updateDocsParameters.sh\n\ndpkg -l jq &> /dev/null || sudo apt install jq\n\nsed -i '/## Available Parameters/q' docs/Parameters.md\necho >> docs/Parameters.md\njq -r '.parameters | to_entries[] | \"### \" + .key + \"\\n\\n\" + .value.metadata.description + \"\\n\\nType: \" + .value.type + \"\\n\\nPossible Values: \" + (.value.allowedValues | @text) + \"\\n\\nDefault: \" + (.value.defaultValue | @text) + \"\\n\\n\"' azuredeploy.json >> docs/Parameters.md "
  },
  {
    "path": "loadtest/Azure_Login.md",
    "content": "# Login to Azure\n\nBefore we start a load test session we need to first veriy that we are\nlogged in to Azure using the CLI.\n\n## Azure Login\n\n``` bash\naz login --username $AZURE_USERNAME --password $AZURE_PASWORD\n```\n\nNote that if your username or password has any special characters in\nit, such as '$' this may fail. You can login using a browser using `az login`.\n\n``` bash\naz account set --subscription $AZURE_SUBSCRIPTION_ID\n```\n\n## Validation\n\n``` bash\naz account show\n```\n\nResults:\n\n```\n{\n  \"environmentName\": \"AzureCloud\",\n  \"id\": \"325e7c34-99fb-4190-aa87-1df746c67705\",\n  \"isDefault\": true,\n  \"name\": \"Ross Dev Account\",\n  \"state\": \"Enabled\",\n  \"tenantId\": \"72f988bf-86f1-41af-91ab-2d7cd011db47\",\n  \"user\": {\n    \"name\": \"rogardle@microsoft.com\",\n    \"type\": \"user\"\n  }\n}\n```\n\n"
  },
  {
    "path": "loadtest/Deploy_Load_Test_VM.md",
    "content": "# Setting up a test host\n\nTo run load tests using the resources in this directory, you'll want\nto spin up an Ubuntu VM (let's call it the jMeter host) in your Azure\nsubscription. This should be located in the same region as your Moodle\ncluster in order to avoid egress charges. Once your VM is ready, you\nneed to install Java and [jMeter](https://jmeter.apache.org/).\n\n## Prerequisites\n\nTo make things consistent across different sessions load testing Moodle we\nshould [configure the moodle environment](../docs/Preparation.md).\n\nWe will want to use a consistent resource group name in order to avoid\nwasting resource in these tests:\n\n```\nMOODLE_RG_NAME=loadtest\n```\n\nAnd we'll need a name for our load test VM:\n\n```\nMOODLE_LOAD_TEST_VM_NAME=LoadTestVM\n```\n\n## Deploy the Load Test VM\n\nFirst you need a resource group within which all your resources will be deployed.\n\n``` bash\naz group create --name $MOODLE_RG_NAME --location $MOODLE_RG_LOCATION\n```\n\nNow we can create our VM in this group. The following command will\ncreate the VM and, if necessary, generate the SSH keys.\n\n``` bash\naz vm create --resource-group $MOODLE_RG_NAME --name $MOODLE_LOAD_TEST_VM_NAME --image UbuntuLTS --generate-ssh-keys\n```\n\nResults:\n\n``` json\n{\n  \"fqdns\": \"\",\n  \"id\": \"/subscriptions/325e7c34-99fb-4190-aa87-1df746c67705/resourceGroups/loadtestvm/providers/Microsoft.Compute/virtualMachines/LoadTestVM\",\n  \"location\": \"southcentralus\",\n  \"macAddress\": \"00-0D-3A-70-91-57\",\n  \"powerState\": \"VM running\",\n  \"privateIpAddress\": \"10.0.0.4\",\n  \"publicIpAddress\": \"13.84.131.173\",\n  \"resourceGroup\": \"loadtestvm\",\n  \"zones\": \"\"\n}\n```\n\nYou will need the IP number from this output. For convenience we'll\nplace it into an environment variable:\n\n``` bash\nipAddress=$(az network public-ip show --name ${MOODLE_LOAD_TEST_VM_NAME}PublicIP --resource-group $MOODLE_RG_NAME --query \"ipAddress\" --output tsv)\necho $ipAddress\n```\n\nWe can now connect to the VM using ssh, and run commands. The first thing we want to do is pull down the Moodle on Azure repo. Since this document is used to automatically run tests all our commands need to be non-interactive. We will therefore skip the host key validation step. Note that you should never do this in a production environment (remove `-o StrictHostKeyChecking=no`):\n\n``` bash\nssh -o StrictHostKeyChecking=no $ipAddress \"rm -Rf Moodle; git clone git://github.com/Azure/Moodle.git\"\n```\n\nNow we can install the load testing scripts, we will have these loaded\nvia the `.profile` so that they are always availble.\n\n``` bash\nssh $ipAddress 'echo \". ~/Moodle/loadtest/loadtest.sh\" >> ~/.profile'\n```\n\nThis script provides some helper functions for installing dependencies\non the VM.\n\n``` bash\nssh $ipAddress 'install_java_and_jmeter; install_az_cli'\n```\n\nWe need to login to Azure using the CLI. The command below is\nconvenient but is not secure since it stores your password in clear\ntext in an environment variable. However, it is convenient for test\npurposes.\n\n``` bash\nssh $ipAddress \"az login --username $AZURE_LOGIN --password $AZURE_PASSWORD; az account set --subscription $AZURE_SUBSCRIPTION_ID\"\n```\n\n## Validation\n\nFinally, we will verify that key dependencies have been installed. First lets check Java is present:\n\n``` bash\nssh -o StrictHostKeyChecking=no $ipAddress \"java -version\"\n```\n\nResults:\n\n```\nopenjdk version \"1.8.0_151\"\nOpenJDK Runtime Environment (build 1.8.0_151-8u151-b12-0ubuntu0.16.04.2-b12)\nOpenJDK 64-Bit Server VM (build 25.151-b12, mixed mode)\n```\n\nWe will also need to confirm the Azure CLI is present:\n\n``` bash\nssh -o StrictHostKeyChecking=no $ipAddress \"if hash az 2>/dev/null; then echo \"Azure CLI Installed\"; else echo \"Missing dependency: Azure CLI\"; fi\"\n```\n\nResults:\n\n```\nAzure CLI Installed\n```\n\n\n\n\n"
  },
  {
    "path": "loadtest/README.md",
    "content": "# Load-Testing Deployed Moodle Cluster\n\nThis directory currently contains utility scripts, a Moodle test\ncourse, and an Apache jMeter test plan that can be used to load-test a\nMoodle cluster on Azure using the Azure Resource Manager templates in\nthis repository.\n\n## Prerequisites\n\nTo run load tests using the resources in this directory, you'll want\nto spin up a VM to manage the [Load Test](Deploy_Load_Test_VM.md) in\nyour Azure subscription. This VM will generate the traffic, running it\nin Azure will minimize network charges.\n\n## Deploying Moodle using templates and running load test\n\nOnce dependencies are installed, you can initiate the load testing\nprocess by using included utility scripts. These scripts will:\n\n* deploy a Moodle cluster\n* set up the test course in Moodle\n* enrol students for the course\n* run a synthetic test workload using jMeter\n* teardown the test cluster\n\nUse the function `deploy_run_test1_teardown` to perform all these\nsteps. This function takes 18 parameters in the following order:\n\nSee the included example `run_load_test_example` in\n`loadtest/loadtest.sh`. At the time of writing this example is\nconfigured as follows:\n\n``` bash\nssh $ipAddress \"az login --username $AZURE_LOGIN --password $AZURE_PASSWORD; az account set --subscription $AZURE_SUBSCRIPTION_ID; run_load_test_example\"\n```\n\nRunning this example will deploy a cluster with the following configuration:\n\n* Apache web server\n* Standard_DS2_v2 Azure VM SKU\n* mysql database (with 200 DTU and 125GB DB size)\n* NFS file share (with 2 disks and 128GB disk size each)\n* uses your SSH pub key in `~/.ssh/id_rsa`\n\n[NOTE ON SSH KEYS] Ensure your `~/.ssh/id_rsa` has been  added to ssh-agent using `eval $(ssh-agent)` and `ssh-add`).\n\nOnce the Moodle cluster is deployed and configured with course and\nstudent data (using [moosh](https://moosh-online.com/) it will run the\nsynthetic workload with designated number of concurrent threads (in\nthe example we use 1600 thread) for the designated duration and rampup\ntime (18000 seconds = 5 hours duration, 4800 seconds rampup time in\nthe example).\n\n## Test plans\n\nWe'd like to offer test plans that are as realistic as possible, so that potential\nMoodle users on Azure can have better confidence with their Moodle deployment on Azure.\nWe are just starting out in that direction and here are descriptions of currently\navailable test plans.\n\n### Simple Scenario [simple-test-1.jmx](./simple-test-1.jmx)\n\nThis test plan is a simple scenario that performs the following operations repeatedly:\n\n* Login to the Moodle site\n* View the test course\n* View any resource if any\n* View a forum in the test course\n* View any forum discussion\n* Post a new discussion topic\n* Take a quiz and submit\n\nThe scripts in [loadtest.sh](./loadtest.sh) are tailored for this test plan.\n\n### Moodle Data Stress Testing [simple-test-2.jmx](./simple-test-2.jmx)\n\nCurrently [loadtest.sh](./loadtest.sh) doesn't have any tailored scripts for this\ntest plan. Therefore, this test plan will need to be executed by issuing the\nactual jmeter command with properly modified parameters manually, or it'd be\ngreatly appreciated if someone can contribute better support for this test plan\nin [loadtest.hs](./loadtest.sh).\n\nThe purpose of this test plan is to try stressing the moodledata directory\nin a shared file system (either a gluster volume or an NFS share, depending\non the choice). Initially attaching a random file in a forum discussion post\nwas tried, but for some reason (probably due to my lack of understanding\nin PHP/web interaction), files were not attached. I instead tried to upload\nrandom files to each test Moodle users's Private Files area, and it did work.\nThis test plan basically performs the following operations repeatedly:\n\n* Login to the Moodle site\n* Open the Moodle user's Private Files repository\n* Upload a randomly generated file (of a random size within a hard-coded range)\n* Save the change\n\nThis way, we were able to populate the shared moodledata directory with\nrandom files in Moodle users' Private Files repositories. The mechanism\nto generate random files is not so efficient, so that's currently what\nslows down the upload speed, and any improvement in that BeanShell preprocessor\ncode would be great. Note that the uploaded files have to be different.\nMoodle seems so good at deduplicating that a single file uploaded multiple\ntimes by different users won't increase the file system usage beyond its\nsingle copy.\n\nIt'd be also great if we add a download operation step in the test plan,\nand it's left as a future work item.\n\n### Latency-Sensitive Stress Testing [time-gated-exam-test.jmx](./time-gated-exam-test.jmx)\n\nThis test stresses the deployed Moodle cluster with 1000 emulated students\ntrying to get in an exam (quiz) that's initially closed and will be opened\nat the designated exam start time (have to be manually set on the test course's\ncorresponding quiz's Settings). Once the exam start time passes, each emulated\nstudent continues taking the exam for 10 times.\n\nThis test has been used to find out how responsive\na deployed Moodle cluster can be on very latency-sensitive workloads. We've been\nusing this test with different file server types to find out which file server\ntype offers best response times.\n\n## Please contribute\n\nIt'd be great if we have more test plans, and make other parameters configurable (for\nexample, make the auto-scaling thresholds configurable, which actually requires\nsome changes in the templates as well). The currently available test plans\nalso have hard-coded database type (JDBC connection string) that won't work\nfor Postgres SQL server, so making it work would be also greatly appreciated.\nAlso, if you run this load test with any parameters, it'd be great to share\nthe numeric results so that we can have more performance data on various\nconfigurations. Here is [a link to an Excel spreadsheet](https://1drv.ms/x/s!Aj6KpM6lFGAjgd4D6IV8_6M42q9omA)\nwhere anyone can share their load testing results.\n\n## Acknowledgement\n\nThe original test course and the test plan were generously provided by\n[Catalyst](https://github.com/catalyst) as part of this template modernization\nproject. jMeter is a great load testing tool, and also thanks to [moosh](http://moosh-online.com/),\nthe whole process could be automated without too much difficulty, which was\nreally nice.\n"
  },
  {
    "path": "loadtest/azuredeploy.parameters.loadtest.defaults.json",
    "content": "{\n    \"$schema\": \"https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#\",\n    \"contentVersion\": \"1.0.0.0\",\n    \"parameters\": {\n        \"autoscaleVmSku\":              { \"value\": \"__WEB_VM_SKU__\" },\n        \"dbServerType\":                { \"value\": \"__DB_SERVER_TYPE__\" },\n        \"mysqlPgresVcores\":            { \"value\": 2 },\n        \"mysqlPgresStgSizeGB\":         { \"value\": 125 },\n        \"webServerType\":               { \"value\": \"__WEB_SERVER_TYPE__\" },\n        \"fileServerType\":              { \"value\": \"__FILE_SERVER_TYPE__\" },\n        \"fileServerDiskCount\":         { \"value\": 2 },\n        \"fileServerDiskSize\":          { \"value\": 128 },\n        \"redisDeploySwitch\":           { \"value\": false },\n        \"sshPublicKey\":                { \"value\": \"__SSH_PUB_KEY__\" }\n    }\n}\n"
  },
  {
    "path": "loadtest/loadtest.sh",
    "content": "#!/bin/bash\n\n# This is not fully tested. Just documenting what's needed.\nfunction install_java_and_jmeter\n{\n    sudo apt update || return 1\n    sudo apt install -y openjdk-8-jdk || return 1\n\n    wget -O apache-jmeter-4.0.tgz https://archive.apache.org/dist/jmeter/binaries/apache-jmeter-4.0.tgz || return 1\n    tar xfz apache-jmeter-4.0.tgz -C ~\n    mkdir -p ~/bin\n    ln -s ~/apache-jmeter-4.0/bin/jmeter ~/bin/jmeter\n    rm apache-jmeter-4.0.tgz\n\n    wget -O mysql-connector-java-5.1.45.tar.gz https://dev.mysql.com/get/Downloads/Connector-J/mysql-connector-java-5.1.45.tar.gz || return 1\n    tar xfz mysql-connector-java-5.1.45.tar.gz\n    mv mysql-connector-java-5.1.45/mysql-connector-java-5.1.45-bin.jar ~/apache-jmeter-4.0/lib\n    rm -rf mysql-connector-java-5.1.45*\n\n    wget -O postgres-42.2.1.jar https://jdbc.postgresql.org/download/postgresql-42.2.1.jar || return 1\n    mv postgres-42.2.1.jar ~/apache-jmeter-4.0/lib\n\n    # Have to have jmeter plugins manager and have it download the needed plugins in advance...\n    wget -O jmeter-plugins-manager-0.19.jar http://search.maven.org/remotecontent?filepath=kg/apc/jmeter-plugins-manager/0.19/jmeter-plugins-manager-0.19.jar || return 1\n    mv jmeter-plugins-manager-0.19.jar ~/apache-jmeter-4.0/lib/ext\n\n    wget -O cmdrunner-2.0.jar http://search.maven.org/remotecontent?filepath=kg/apc/cmdrunner/2.0/cmdrunner-2.0.jar || return 1\n    mv cmdrunner-2.0.jar ~/apache-jmeter-4.0/lib\n    java -cp ~/apache-jmeter-4.0/lib/ext/jmeter-plugins-manager-0.19.jar org.jmeterplugins.repository.PluginManagerCMDInstaller\n    # TODO Hard-coded .jmx file here. Do this for each individual .jmx file\n    wget -O tmp-for-plugin-install.jmx https://raw.githubusercontent.com/Azure/Moodle/master/loadtest/simple-test-1.jmx || return 1\n    ~/apache-jmeter-4.0/bin/PluginsManagerCMD.sh install-for-jmx tmp-for-plugin-install.jmx\n    rm tmp-for-plugin-install.jmx\n}\n\nfunction install_az_cli\n{\n    local az_repo=$(lsb_release -cs)\n    \n    sudo mkdir -p /etc/apt/keyrings\n    curl -sLS https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor | sudo tee /etc/apt/keyrings/microsoft.gpg > /dev/null\n    sudo chmod go+r /etc/apt/keyrings/microsoft.gpg\n    echo \"deb [arch=`dpkg --print-architecture` signed-by=/etc/apt/keyrings/microsoft.gpg] https://packages.microsoft.com/repos/azure-cli/ $az_repo main\" | sudo tee /etc/apt/sources.list.d/azure-cli.list\n\n    export DEBIAN_FRONTEND=noninteractive\n    sudo apt-get -qq -o=Dpkg::Use-Pty=0 update || return 1\n    sudo apt-get --yes --no-install-recommends \\\n        -qq -o=Dpkg::Use-Pty=0 \\\n        -o Dpkg::Options::=\"--force-confdef\" \\\n        -o Dpkg::Options::=\"--force-confold\" \\\n        install azure-cli || return 1\n}\n\nfunction check_if_logged_on_azure\n{\n    az account show --query id -o tsv > /dev/null 2>&1\n    if [ $? != \"0\" ]; then\n        echo \"Not logged on to Azure. Run 'az login' first and make sure subscription is set to your desired one.\"\n        return 1\n    fi\n}\n\nfunction show_command_to_run\n{\n    echo \"Running command: $*\"\n}\n\nfunction check_db_sku_params\n{\n    local vcores=${1}\n    local size=${2}   # In GB\n\n    if [ \"$vcores\" != 1 -a \"$vcores\" != 2 -a \"$vcores\" != 4 -a \"$vcores\" != 8 -a \"$vcores\" != 16 -a \"$vcores\" != 32 ]; then\n        echo \"Invalid vCores ($vcores). Only allowed are 1, 2, 4, 8, 16, 32.\"\n        return 1\n    fi\n    if [ -z \"${size##*[!0-9]*}\" ] || [ \"$size\" -lt 5 ] || [ \"$size\" -gt 1024 ]; then\n        echo \"Invalid DB size ($size). Only allowed are 5, 6, 7, ..., 1024.\"\n        return 1\n    fi\n    # TODO Add other SKU params: Tiers (Basic/GeneralPurpose/MemoryOptimized), HW family (Gen4/Gen5)\n}\n\n# TODO hard-coded Azure location in global variable. Parametrize this later.\nMOODLE_RG_LOCATION=southcentralus\n\nfunction deploy_moodle_with_some_parameters\n{\n    check_if_logged_on_azure || return 1\n\n    local resource_group=${1}   # Azure resource group where templates will be deployed\n    local template_url=${2}     # Github URL of the top template to deploy\n    local parameters_template_file=${3} # Local parameter template file\n    local web_server_type=${4}  # E.g., apache or nginx\n    local web_vm_sku=${5}       # E.g., Standard_DS2_v2\n    local db_server_type=${6}   # E.g., mysql or postgres\n    local db_vcores=${7}        # 1, 2, 4, 8, 16, 32 only\n    local db_size_gb=${8}       # 5 to 1024, integer only\n    local file_server_type=${9} # E.g., nfs or gluster\n    local file_server_disk_count=${10}  # 2, 3, 4\n    local file_server_disk_size=${11}   # in GB\n    local redis_cache=${12}     # Redis cache choice. Currently 'true' or 'false' only.\n    local ssh_pub_key=${13}     # Your ssh authorized_keys content\n    local no_wait_flag=${14}    # Must be \"--no-wait\" to be passed to az\n\n    check_db_sku_params $db_vcores $db_size_gb || return 1\n\n    local cmd=\"az group create --resource-group $resource_group --location $MOODLE_RG_LOCATION\"\n    show_command_to_run $cmd\n    eval $cmd || return 1\n\n    local deployment_name=\"${resource_group}-deployment\"\n    local cmd=\"az deployment group create --resource-group $resource_group --name $deployment_name $no_wait_flag --template-uri $template_url --parameters @$parameters_template_file webServerType=$web_server_type autoscaleVmSku=$web_vm_sku dbServerType=$db_server_type mysqlPgresVcores=$db_vcores mysqlPgresStgSizeGB=$db_size_gb fileServerType=$file_server_type fileServerDiskCount=$file_server_disk_count fileServerDiskSize=$file_server_disk_size redisDeploySwitch=$redis_cache sshPublicKey='$ssh_pub_key'\"\n    show_command_to_run $cmd\n    eval $cmd\n}\n\nfunction delete_resource_group\n{\n    check_if_logged_on_azure || return 1\n\n    local resource_group=${1}\n    local cmd=\"az group delete --resource-group $resource_group\"\n    show_command_to_run $cmd\n    eval $cmd\n}\n\nfunction install_moosh\n{\n    # 'composer install' keeps failing, so try apt...\n    sudo apt-add-repository \"deb http://ppa.launchpad.net/zabuch/ppa/ubuntu $(lsb_release -sc) main\"\n    sudo apt-get update || true\n    sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys CA1F0167ECFEA95\n    sudo apt-get install --assume-yes moosh\n\n    # sudo apt update || return 1\n    # sudo apt install -y composer || return 1\n    # cd ~\n    # git clone git://github.com/tmuras/moosh.git || return 1\n    # cd moosh\n    # composer install || sleep 30 && composer install || sleep 30 && composer install || return 1\n    # mkdir -p ~/bin\n    # ln -s $PWD/moosh.php ~/bin/moosh\n}\n\nMOODLE_PATH=/moodle/html/moodle\n\nfunction delete_course\n{\n    local course_id=${1}\n\n    sudo -u www-data moosh --moodle-path=$MOODLE_PATH course-delete $course_id\n}\n\nfunction create_course\n{\n    local course_id=${1}\n\n    sudo -u www-data moosh --moodle-path=$MOODLE_PATH course-create --idnumber=$course_id empty@test.course\n}\n\nfunction restore_course_from_url\n{\n    local url=${1}\n\n    wget $url -O backup_to_restore.mbz\n    sudo -u www-data moosh --moodle-path=$MOODLE_PATH course-restore backup_to_restore.mbz 1\n}\n\nfunction create_2000_test_users_and_enroll_them_in_course\n{\n    local course_id=${1}\n    local password=${2}\n\n    # TODO ugly...\n    sudo -u www-data moosh --moodle-path=$MOODLE_PATH user-create -p $password m_azuretestuser_{1..200}\n    sudo -u www-data moosh --moodle-path=$MOODLE_PATH user-create -p $password m_azuretestuser_{201..400}\n    sudo -u www-data moosh --moodle-path=$MOODLE_PATH user-create -p $password m_azuretestuser_{401..600}\n    sudo -u www-data moosh --moodle-path=$MOODLE_PATH user-create -p $password m_azuretestuser_{601..800}\n    sudo -u www-data moosh --moodle-path=$MOODLE_PATH user-create -p $password m_azuretestuser_{801..1000}\n    sudo -u www-data moosh --moodle-path=$MOODLE_PATH user-create -p $password m_azuretestuser_{1001..1200}\n    sudo -u www-data moosh --moodle-path=$MOODLE_PATH user-create -p $password m_azuretestuser_{1201..1400}\n    sudo -u www-data moosh --moodle-path=$MOODLE_PATH user-create -p $password m_azuretestuser_{1401..1600}\n    sudo -u www-data moosh --moodle-path=$MOODLE_PATH user-create -p $password m_azuretestuser_{1601..1800}\n    sudo -u www-data moosh --moodle-path=$MOODLE_PATH user-create -p $password m_azuretestuser_{1801..2000}\n\n    sudo -u www-data moosh --moodle-path=$MOODLE_PATH course-enrol $course_id m_azuretestuser_{1..200}\n    sudo -u www-data moosh --moodle-path=$MOODLE_PATH course-enrol $course_id m_azuretestuser_{201..400}\n    sudo -u www-data moosh --moodle-path=$MOODLE_PATH course-enrol $course_id m_azuretestuser_{401..600}\n    sudo -u www-data moosh --moodle-path=$MOODLE_PATH course-enrol $course_id m_azuretestuser_{601..800}\n    sudo -u www-data moosh --moodle-path=$MOODLE_PATH course-enrol $course_id m_azuretestuser_{801..1000}\n    sudo -u www-data moosh --moodle-path=$MOODLE_PATH course-enrol $course_id m_azuretestuser_{1001..1200}\n    sudo -u www-data moosh --moodle-path=$MOODLE_PATH course-enrol $course_id m_azuretestuser_{1201..1400}\n    sudo -u www-data moosh --moodle-path=$MOODLE_PATH course-enrol $course_id m_azuretestuser_{1401..1600}\n    sudo -u www-data moosh --moodle-path=$MOODLE_PATH course-enrol $course_id m_azuretestuser_{1601..1800}\n    sudo -u www-data moosh --moodle-path=$MOODLE_PATH course-enrol $course_id m_azuretestuser_{1801..2000}\n}\n\nfunction hide_course_overview_block_for_jmeter_test\n{\n    # \"myoverview\" is the registered name of the \"Course overview\" block\n    sudo -u www-data moosh --moodle-path=$MOODLE_PATH block-manage hide myoverview\n}\n\n# TODO hard-coded values...\nLOADTEST_BASE_URI=https://raw.githubusercontent.com/Azure/Moodle/master/loadtest\nMOODLE_TEST_USER_PASSWORD='testUserP@$$w0rd'\n\nfunction setup_test_course_and_users\n{\n    install_moosh\n    # TODO hard-coded test course backup location\n    restore_course_from_url $LOADTEST_BASE_URI/moodle-on-azure-test-course-1.mbz\n    local course_id=2  # TODO Fix this hard-coded course id #. Should be retrieved from the previous restore_course_from_url output\n    local password=$MOODLE_TEST_USER_PASSWORD   # TODO parameterize\n    create_2000_test_users_and_enroll_them_in_course $course_id $password\n    hide_course_overview_block_for_jmeter_test\n}\n\nfunction run_cmd_on_remote_host\n{\n    local func_cmd=${1}   # E.g., install_moosh or 'delete_course 2'\n    local ssh_dest=${2}   # E.g., azureadmin@10.2.3.4\n    local port=${3:-22}   # E.g., 2222\n\n    local cmd=\"ssh -o 'StrictHostKeyChecking no' -p $port $ssh_dest 'wget $LOADTEST_BASE_URI/loadtest.sh -O loadtest.sh; source loadtest.sh; $func_cmd'\"\n    show_command_to_run $cmd\n    eval $cmd\n}\n\nfunction run_simple_test_1_on_resource_group\n{\n    local resource_group=${1}       # Azure resource group where Moodle templates were deployed\n    local test_threads_count=${2}   # E.g., 400, 800, ...\n    local test_rampup_time_sec=${3} # E.g., 900 (should be long enough for # threads above)\n    local test_run_time_sec=${4}    # E.g., 3600 for 1 hour\n    local setup_test_course_users_flag=${5} # Run setup_test_course_and_users on moodle_host if nonzero\n\n    sudo apt update; sudo apt install -y jq\n    local deployment=\"${resource_group}-deployment\"\n    local output=$(az deployment group show -g $resource_group -n $deployment)\n    local moodle_host=$(echo $output | jq -r .properties.outputs.siteURL.value)\n    local db_host=$(echo $output | jq -r .properties.outputs.databaseDNS.value)\n    local moodle_db_user=$(echo $output | jq -r .properties.outputs.moodleDbUsername.value)\n    local moodle_db_pass=$(echo $output | jq -r .properties.outputs.moodleDbPassword.value)\n    local moodle_user_pass=$MOODLE_TEST_USER_PASSWORD    # TODO parameterize\n\n    if [ -n \"$setup_test_course_users_flag\" ]; then\n        local moodle_controller_ip=$(echo $output | jq -r .properties.outputs.controllerInstanceIP.value)\n        run_cmd_on_remote_host setup_test_course_and_users azureadmin@${moodle_controller_ip}\n    fi\n\n    mkdir -p test_outputs\n\n    local prefix=\"test_outputs/simple_test_1_$(date +%Y%m%d%H%M%S)\"\n    echo $output | jq . > ${prefix}.deployment.json\n\n    export JVM_ARGS=\"-Xms1024m -Xmx4096m\"\n    local cmd=\"jmeter -n -t simple-test-1.jmx -l ${prefix}.jmeter.results.txt -j ${prefix}.jmeter.log -e -o ${prefix}.jmeter.report -Jhost=${moodle_host} -Jdb_host=${db_host} -Jdb_user=${moodle_db_user} '-Jdb_pass=${moodle_db_pass}' '-Jmoodle_user_pass=${moodle_user_pass}' -Jthreads=${test_threads_count} -Jrampup=${test_rampup_time_sec} -Jruntime=${test_run_time_sec}\"\n    show_command_to_run $cmd\n    eval $cmd\n}\n\nfunction deallocate_services_in_resource_group\n{\n    local rg=${1}\n\n    # Deallocate VMSS's\n    local scalesets=$(az vmss list -g $rg --query [].name -o tsv)\n    for scaleset in $scalesets; do\n        local cmd=\"az vmss deallocate -g $rg --name $scaleset\"\n        show_command_to_run $cmd\n        eval $cmd\n    done\n\n    # Deallocate VMs\n    local cmd=\"az vm deallocate --ids $(az vm list -g $rg --query [].id -o tsv)\"\n    show_command_to_run $cmd\n    eval $cmd\n\n    # Stopping DBs and redis cache is currently not possible on Azure.\n}\n\nfunction deploy_run_test1_teardown\n{\n    local resource_group=${1}\n    local location=${2}\n    local template_url=${3}\n    local parameters_template_file=${4}\n    local web_server_type=${5}\n    local web_vm_sku=${6}\n    local db_server_type=${7}\n    local db_vcores=${8}\n    local db_size_gb=${9}\n    local file_server_type=${10}\n    local file_server_disk_count=${11}\n    local file_server_disk_size=${12}\n    local redis_cache=${13}\n    local ssh_pub_key=${14}\n    local test_threads_count=${15}\n    local test_rampup_time_sec=${16}\n    local test_run_time_sec=${17}\n    local delete_resource_group_flag=${18}  # Any non-empty string is considered true\n\n    MOODLE_RG_LOCATION=$location\n    deploy_moodle_with_some_parameters $resource_group $template_url $parameters_template_file $web_server_type $web_vm_sku $db_server_type $db_vcores $db_size_gb $file_server_type $file_server_disk_count $file_server_disk_size $redis_cache \"$ssh_pub_key\" || return 1\n    run_simple_test_1_on_resource_group $resource_group $test_threads_count $test_rampup_time_sec $test_run_time_sec 1 || return 1\n    if [ -n \"$delete_resource_group_flag\" ]; then\n        az group delete -g $resource_group -y\n    else\n        deallocate_services_in_resource_group $resource_group\n    fi\n}\n\nfunction check_ssh_agent_and_added_key\n{\n    ssh-add -l\n    if [ $? != \"0\" ]; then\n        echo \"No ssh key added to ssh-agent or no ssh-agent is running. Make sure to run ssh-agent (eval `ssh-agent`) and add the correct ssh key (usually just ssh-add will do), so that remote commands execution through ssh doesn't prompt for interactive password.\"\n        return 1\n    fi\n}\n\nfunction run_load_test_example\n{\n    check_ssh_agent_and_added_key || return 1\n\n    deploy_run_test1_teardown ltest6 southcentralus https://raw.githubusercontent.com/Azure/Moodle/master/azuredeploy.json azuredeploy.parameters.loadtest.defaults.json apache Standard_DS2_v2 mysql 4 125 nfs 2 128 false \"$(cat ~/.ssh/authorized_keys)\" 1600 4800 18000\n}\n\nfunction run_load_test_postgres\n{\n    check_ssh_agent_and_added_key || return 1\n\n    deploy_run_test1_teardown pgres southcentralus https://raw.githubusercontent.com/Azure/Moodle/master/azuredeploy.json azuredeploy.parameters.loadtest.defaults.json apache Standard_DS2_v2 postgres 16 256 nfs 2 128 false \"$(cat ~/.ssh/authorized_keys)\" 800 2400 36000\n}\n"
  },
  {
    "path": "loadtest/simple-test-1.jmx",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<jmeterTestPlan version=\"1.2\" properties=\"4.0\" jmeter=\"4.0 r1823414\">\n  <hashTree>\n    <TestPlan guiclass=\"TestPlanGui\" testclass=\"TestPlan\" testname=\"Moodle Load Test Plan\" enabled=\"true\">\n      <boolProp name=\"TestPlan.functional_mode\">false</boolProp>\n      <collectionProp name=\"TestPlan.thread_groups\"/>\n      <elementProp name=\"TestPlan.user_defined_variables\" elementType=\"Arguments\" guiclass=\"ArgumentsPanel\" testclass=\"Arguments\" testname=\"Argument List\" enabled=\"true\">\n        <collectionProp name=\"Arguments.arguments\"/>\n      </elementProp>\n      <boolProp name=\"TestPlan.serialize_threadgroups\">false</boolProp>\n      <stringProp name=\"TestPlan.comments\"></stringProp>\n      <stringProp name=\"TestPlan.user_define_classpath\"></stringProp>\n    </TestPlan>\n    <hashTree>\n      <Arguments guiclass=\"ArgumentsPanel\" testclass=\"Arguments\" testname=\"User Defined Variables\" enabled=\"true\">\n        <collectionProp name=\"Arguments.arguments\">\n          <elementProp name=\"login_host\" elementType=\"Argument\">\n            <stringProp name=\"Argument.name\">login_host</stringProp>\n            <stringProp name=\"Argument.value\">${__P(host, lb-cosbae.southcentralus.cloudapp.azure.com)}</stringProp>\n            <stringProp name=\"Argument.metadata\">=</stringProp>\n          </elementProp>\n          <elementProp name=\"host\" elementType=\"Argument\">\n            <stringProp name=\"Argument.name\">host</stringProp>\n            <stringProp name=\"Argument.value\">${__P(host, lb-cosbae.southcentralus.cloudapp.azure.com)}</stringProp>\n            <stringProp name=\"Argument.metadata\">=</stringProp>\n          </elementProp>\n          <elementProp name=\"threads\" elementType=\"Argument\">\n            <stringProp name=\"Argument.name\">threads</stringProp>\n            <stringProp name=\"Argument.value\">${__P(threads,400)}</stringProp>\n            <stringProp name=\"Argument.metadata\">=</stringProp>\n            <stringProp name=\"Argument.desc\">Default 10 (for a quick GUI run)</stringProp>\n          </elementProp>\n          <elementProp name=\"rampup\" elementType=\"Argument\">\n            <stringProp name=\"Argument.name\">rampup</stringProp>\n            <stringProp name=\"Argument.value\">${__P(rampup,900)}</stringProp>\n            <stringProp name=\"Argument.metadata\">=</stringProp>\n            <stringProp name=\"Argument.desc\">Default 120 - 1/15 runtime default rampup</stringProp>\n          </elementProp>\n          <elementProp name=\"runtime\" elementType=\"Argument\">\n            <stringProp name=\"Argument.name\">runtime</stringProp>\n            <stringProp name=\"Argument.value\">${__P(runtime,10800)}</stringProp>\n            <stringProp name=\"Argument.metadata\">=</stringProp>\n            <stringProp name=\"Argument.desc\">Default 1800 = 30 minutes = 30*60 seconds</stringProp>\n          </elementProp>\n          <elementProp name=\"default_connect_timeout\" elementType=\"Argument\">\n            <stringProp name=\"Argument.name\">default_connect_timeout</stringProp>\n            <stringProp name=\"Argument.value\">2000</stringProp>\n            <stringProp name=\"Argument.metadata\">=</stringProp>\n            <stringProp name=\"Argument.desc\">1.2 seconds to a TCP ack</stringProp>\n          </elementProp>\n          <elementProp name=\"default_response_timeout\" elementType=\"Argument\">\n            <stringProp name=\"Argument.name\">default_response_timeout</stringProp>\n            <stringProp name=\"Argument.value\">240000</stringProp>\n            <stringProp name=\"Argument.metadata\">=</stringProp>\n            <stringProp name=\"Argument.desc\">4 Minutes for the page to respond</stringProp>\n          </elementProp>\n          <elementProp name=\"throughput_rate\" elementType=\"Argument\">\n            <stringProp name=\"Argument.name\">throughput_rate</stringProp>\n            <stringProp name=\"Argument.value\">${__P(throughput_rate, 50)}</stringProp>\n            <stringProp name=\"Argument.metadata\">=</stringProp>\n            <stringProp name=\"Argument.desc\">Default 650 should approximate ~ 7k page views / 5 mins cacti sample</stringProp>\n          </elementProp>\n          <elementProp name=\"activity_course_id\" elementType=\"Argument\">\n            <stringProp name=\"Argument.name\">activity_course_id</stringProp>\n            <stringProp name=\"Argument.value\">${__P(activity_course_id, 2)}</stringProp>\n            <stringProp name=\"Argument.metadata\">=</stringProp>\n            <stringProp name=\"Argument.desc\">This must match the course ID of the course with performance activities</stringProp>\n          </elementProp>\n          <elementProp name=\"quiz_delay\" elementType=\"Argument\">\n            <stringProp name=\"Argument.name\">quiz_delay</stringProp>\n            <stringProp name=\"Argument.value\">${__P(quiz_delay, 100)}</stringProp>\n            <stringProp name=\"Argument.desc\">-1 means don&apos;t run quiz</stringProp>\n            <stringProp name=\"Argument.metadata\">=</stringProp>\n          </elementProp>\n          <elementProp name=\"quiz_users\" elementType=\"Argument\">\n            <stringProp name=\"Argument.name\">quiz_users</stringProp>\n            <stringProp name=\"Argument.value\">${__P(quiz_users, 1)}</stringProp>\n            <stringProp name=\"Argument.desc\">Number of quiz users to simulate</stringProp>\n            <stringProp name=\"Argument.metadata\">=</stringProp>\n          </elementProp>\n          <elementProp name=\"groupselect_delay\" elementType=\"Argument\">\n            <stringProp name=\"Argument.name\">groupselect_delay</stringProp>\n            <stringProp name=\"Argument.value\">${__P(groupselect_delay,-1)}</stringProp>\n            <stringProp name=\"Argument.metadata\">=</stringProp>\n          </elementProp>\n          <elementProp name=\"groupselect_users\" elementType=\"Argument\">\n            <stringProp name=\"Argument.name\">groupselect_users</stringProp>\n            <stringProp name=\"Argument.value\">${__P(groupselect_users, 0)}</stringProp>\n            <stringProp name=\"Argument.metadata\">=</stringProp>\n          </elementProp>\n          <elementProp name=\"protocol\" elementType=\"Argument\">\n            <stringProp name=\"Argument.name\">protocol</stringProp>\n            <stringProp name=\"Argument.value\">https</stringProp>\n            <stringProp name=\"Argument.metadata\">=</stringProp>\n          </elementProp>\n          <elementProp name=\"port\" elementType=\"Argument\">\n            <stringProp name=\"Argument.name\">port</stringProp>\n            <stringProp name=\"Argument.value\">443</stringProp>\n            <stringProp name=\"Argument.metadata\">=</stringProp>\n          </elementProp>\n          <elementProp name=\"moodle_user_pass\" elementType=\"Argument\">\n            <stringProp name=\"Argument.name\">moodle_user_pass</stringProp>\n            <stringProp name=\"Argument.value\">${__P(moodle_user_pass,testUserP@$$w0rd)}</stringProp>\n            <stringProp name=\"Argument.metadata\">=</stringProp>\n          </elementProp>\n        </collectionProp>\n      </Arguments>\n      <hashTree/>\n      <ThreadGroup guiclass=\"ThreadGroupGui\" testclass=\"ThreadGroup\" testname=\"Load Test Moodle\" enabled=\"true\">\n        <stringProp name=\"ThreadGroup.on_sample_error\">startnextloop</stringProp>\n        <elementProp name=\"ThreadGroup.main_controller\" elementType=\"LoopController\" guiclass=\"LoopControlPanel\" testclass=\"LoopController\" testname=\"Loop Controller\" enabled=\"true\">\n          <boolProp name=\"LoopController.continue_forever\">false</boolProp>\n          <intProp name=\"LoopController.loops\">-1</intProp>\n        </elementProp>\n        <stringProp name=\"ThreadGroup.num_threads\">${threads}</stringProp>\n        <stringProp name=\"ThreadGroup.ramp_time\">${rampup}</stringProp>\n        <longProp name=\"ThreadGroup.start_time\">1513564238000</longProp>\n        <longProp name=\"ThreadGroup.end_time\">1513564538000</longProp>\n        <boolProp name=\"ThreadGroup.scheduler\">true</boolProp>\n        <stringProp name=\"ThreadGroup.duration\">${runtime}</stringProp>\n        <stringProp name=\"ThreadGroup.delay\">0</stringProp>\n      </ThreadGroup>\n      <hashTree>\n        <CookieManager guiclass=\"CookiePanel\" testclass=\"CookieManager\" testname=\"HTTP Cookie Manager\" enabled=\"true\">\n          <collectionProp name=\"CookieManager.cookies\"/>\n          <boolProp name=\"CookieManager.clearEachIteration\">true</boolProp>\n          <stringProp name=\"CookieManager.policy\">default</stringProp>\n        </CookieManager>\n        <hashTree/>\n        <GenericController guiclass=\"LogicControllerGui\" testclass=\"GenericController\" testname=\"Moodle Tests\" enabled=\"true\"/>\n        <hashTree>\n          <HeaderManager guiclass=\"HeaderPanel\" testclass=\"HeaderManager\" testname=\"HTTP Header Manager\" enabled=\"true\">\n            <collectionProp name=\"HeaderManager.headers\">\n              <elementProp name=\"\" elementType=\"Header\">\n                <stringProp name=\"Header.name\">User-Agent</stringProp>\n                <stringProp name=\"Header.value\">Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0; Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1) ; InfoPath.1; .NET CLR 1.1.4322; .NET CLR 2.0.50727; .NET CLR 3.0.04506.648; .NET CLR 3.5.21022; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729; .NET4.0C; OfficeLiveConnector.1.5; OfficeLivePatch.1.3)</stringProp>\n              </elementProp>\n              <elementProp name=\"\" elementType=\"Header\">\n                <stringProp name=\"Header.name\">Accept</stringProp>\n                <stringProp name=\"Header.value\">*/*</stringProp>\n              </elementProp>\n              <elementProp name=\"\" elementType=\"Header\">\n                <stringProp name=\"Header.name\">Accept-Language</stringProp>\n                <stringProp name=\"Header.value\">en-us</stringProp>\n              </elementProp>\n            </collectionProp>\n          </HeaderManager>\n          <hashTree/>\n          <GenericController guiclass=\"LogicControllerGui\" testclass=\"GenericController\" testname=\"Moodle tests\" enabled=\"true\"/>\n          <hashTree>\n            <UserParameters guiclass=\"UserParametersGui\" testclass=\"UserParameters\" testname=\"User Parameters\" enabled=\"true\">\n              <boolProp name=\"TestElement.per_iteration\">true</boolProp>\n              <collectionProp name=\"UserParameters.names\">\n                <stringProp name=\"VIEWSTATE\">VIEWSTATE</stringProp>\n                <stringProp name=\"jsessionid\">jsessionid</stringProp>\n                <stringProp name=\"-532667892\">RandomNumber</stringProp>\n                <stringProp name=\"-265713450\">username</stringProp>\n                <stringProp name=\"-1797703761\">userRandomChoice</stringProp>\n                <stringProp name=\"1984989421\">sesskey</stringProp>\n                <stringProp name=\"636521343\">testBigFileLocation</stringProp>\n                <stringProp name=\"-1981424811\">testBigFileName</stringProp>\n                <stringProp name=\"775281475\">discussionid</stringProp>\n                <stringProp name=\"-1804370087\">courseList</stringProp>\n                <stringProp name=\"-2078389680\">xx_outputFromSplit</stringProp>\n              </collectionProp>\n              <collectionProp name=\"UserParameters.thread_values\">\n                <collectionProp name=\"1681097958\">\n                  <stringProp name=\"\"></stringProp>\n                  <stringProp name=\"\"></stringProp>\n                  <stringProp name=\"-2046088625\">${__Random(1,2000,ReuseRandomNumber)}</stringProp>\n                  <stringProp name=\"379755733\">m_azuretestuser_${RandomNumber}</stringProp>\n                  <stringProp name=\"1345253498\">${RandomNumber}</stringProp>\n                  <stringProp name=\"-352988409\">${sesskey}</stringProp>\n                  <stringProp name=\"0\"></stringProp>\n                  <stringProp name=\"0\"></stringProp>\n                  <stringProp name=\"0\"></stringProp>\n                  <stringProp name=\"257720200\">${__V(course_list_${RandomNumber})}</stringProp>\n                  <stringProp name=\"-306911129\">${__split(${courseList}, course_id,|)}</stringProp>\n                </collectionProp>\n              </collectionProp>\n              <boolProp name=\"UserParameters.per_iteration\">true</boolProp>\n            </UserParameters>\n            <hashTree/>\n            <HTTPSamplerProxy guiclass=\"HttpTestSampleGui\" testclass=\"HTTPSamplerProxy\" testname=\"Goto Moodle\" enabled=\"true\">\n              <elementProp name=\"HTTPsampler.Arguments\" elementType=\"Arguments\" guiclass=\"HTTPArgumentsPanel\" testclass=\"Arguments\" testname=\"Argument List\" enabled=\"true\">\n                <collectionProp name=\"Arguments.arguments\"/>\n              </elementProp>\n              <stringProp name=\"HTTPSampler.domain\">${host}</stringProp>\n              <stringProp name=\"HTTPSampler.port\">${port}</stringProp>\n              <stringProp name=\"HTTPSampler.protocol\">${protocol}</stringProp>\n              <stringProp name=\"HTTPSampler.contentEncoding\"></stringProp>\n              <stringProp name=\"HTTPSampler.path\">/</stringProp>\n              <stringProp name=\"HTTPSampler.method\">GET</stringProp>\n              <boolProp name=\"HTTPSampler.follow_redirects\">true</boolProp>\n              <boolProp name=\"HTTPSampler.auto_redirects\">false</boolProp>\n              <boolProp name=\"HTTPSampler.use_keepalive\">true</boolProp>\n              <boolProp name=\"HTTPSampler.DO_MULTIPART_POST\">false</boolProp>\n              <stringProp name=\"HTTPSampler.embedded_url_re\"></stringProp>\n              <stringProp name=\"HTTPSampler.implementation\">Java</stringProp>\n              <stringProp name=\"HTTPSampler.connect_timeout\">${default_connect_timeout}</stringProp>\n              <stringProp name=\"HTTPSampler.response_timeout\">${default_response_timeout}</stringProp>\n            </HTTPSamplerProxy>\n            <hashTree>\n              <ConstantTimer guiclass=\"ConstantTimerGui\" testclass=\"ConstantTimer\" testname=\"Constant Timer\" enabled=\"false\">\n                <stringProp name=\"ConstantTimer.delay\">1000</stringProp>\n              </ConstantTimer>\n              <hashTree/>\n              <GaussianRandomTimer guiclass=\"GaussianRandomTimerGui\" testclass=\"GaussianRandomTimer\" testname=\"Gaussian Random Timer\" enabled=\"true\">\n                <stringProp name=\"ConstantTimer.delay\">8000</stringProp>\n                <stringProp name=\"RandomTimer.range\">4000</stringProp>\n              </GaussianRandomTimer>\n              <hashTree/>\n            </hashTree>\n            <HTTPSamplerProxy guiclass=\"HttpTestSampleGui\" testclass=\"HTTPSamplerProxy\" testname=\"POST login\" enabled=\"true\">\n              <elementProp name=\"HTTPsampler.Arguments\" elementType=\"Arguments\" guiclass=\"HTTPArgumentsPanel\" testclass=\"Arguments\" testname=\"Argument List\" enabled=\"true\">\n                <collectionProp name=\"Arguments.arguments\">\n                  <elementProp name=\"username\" elementType=\"HTTPArgument\">\n                    <stringProp name=\"HTTPArgument.always_encode\">true</stringProp>\n                    <stringProp name=\"Argument.value\">${username}</stringProp>\n                    <stringProp name=\"Argument.name\">username</stringProp>\n                    <stringProp name=\"Argument.metadata\">=</stringProp>\n                    <stringProp name=\"Argument.use_equals\">true</stringProp>\n                    <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                  </elementProp>\n                  <elementProp name=\"password\" elementType=\"HTTPArgument\">\n                    <stringProp name=\"HTTPArgument.always_encode\">true</stringProp>\n                    <stringProp name=\"Argument.value\">${moodle_user_pass}</stringProp>\n                    <stringProp name=\"Argument.name\">password</stringProp>\n                    <stringProp name=\"Argument.metadata\">=</stringProp>\n                    <stringProp name=\"Argument.use_equals\">true</stringProp>\n                    <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                  </elementProp>\n                  <elementProp name=\"Login\" elementType=\"HTTPArgument\">\n                    <boolProp name=\"HTTPArgument.always_encode\">true</boolProp>\n                    <stringProp name=\"Argument.value\">Login</stringProp>\n                    <stringProp name=\"Argument.metadata\">=</stringProp>\n                    <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                    <stringProp name=\"Argument.name\">Login</stringProp>\n                  </elementProp>\n                </collectionProp>\n              </elementProp>\n              <stringProp name=\"HTTPSampler.domain\">${login_host}</stringProp>\n              <stringProp name=\"HTTPSampler.port\">${port}</stringProp>\n              <stringProp name=\"HTTPSampler.protocol\">${protocol}</stringProp>\n              <stringProp name=\"HTTPSampler.contentEncoding\"></stringProp>\n              <stringProp name=\"HTTPSampler.path\">/login/index.php</stringProp>\n              <stringProp name=\"HTTPSampler.method\">POST</stringProp>\n              <boolProp name=\"HTTPSampler.follow_redirects\">true</boolProp>\n              <boolProp name=\"HTTPSampler.auto_redirects\">false</boolProp>\n              <boolProp name=\"HTTPSampler.use_keepalive\">true</boolProp>\n              <boolProp name=\"HTTPSampler.DO_MULTIPART_POST\">false</boolProp>\n              <boolProp name=\"HTTPSampler.image_parser\">true</boolProp>\n              <stringProp name=\"HTTPSampler.embedded_url_re\"></stringProp>\n              <stringProp name=\"HTTPSampler.implementation\">Java</stringProp>\n              <stringProp name=\"HTTPSampler.connect_timeout\">${default_connect_timeout}</stringProp>\n              <stringProp name=\"HTTPSampler.response_timeout\">${default_response_timeout}</stringProp>\n            </HTTPSamplerProxy>\n            <hashTree>\n              <ResponseAssertion guiclass=\"AssertionGui\" testclass=\"ResponseAssertion\" testname=\"Assertion - Logged in Screen\" enabled=\"true\">\n                <collectionProp name=\"Asserion.test_strings\">\n                  <stringProp name=\"-1544167836\">You are logged in as </stringProp>\n                </collectionProp>\n                <intProp name=\"Assertion.test_type\">2</intProp>\n                <stringProp name=\"Assertion.test_field\">Assertion.response_data</stringProp>\n                <boolProp name=\"Assertion.assume_success\">false</boolProp>\n                <stringProp name=\"Assertion.custom_message\"></stringProp>\n              </ResponseAssertion>\n              <hashTree/>\n              <XPathExtractor guiclass=\"XPathExtractorGui\" testclass=\"XPathExtractor\" testname=\"Get sesskey\" enabled=\"true\">\n                <stringProp name=\"XPathExtractor.default\"></stringProp>\n                <stringProp name=\"XPathExtractor.refname\">sesskey</stringProp>\n                <stringProp name=\"XPathExtractor.xpathQuery\">//*[@name=&apos;sesskey&apos;][1]/@value</stringProp>\n                <boolProp name=\"XPathExtractor.validate\">false</boolProp>\n                <boolProp name=\"XPathExtractor.tolerant\">true</boolProp>\n                <boolProp name=\"XPathExtractor.namespace\">false</boolProp>\n                <stringProp name=\"XPathExtractor.matchNumber\">-1</stringProp>\n              </XPathExtractor>\n              <hashTree/>\n              <ConstantTimer guiclass=\"ConstantTimerGui\" testclass=\"ConstantTimer\" testname=\"Constant Timer\" enabled=\"false\">\n                <stringProp name=\"ConstantTimer.delay\">1000</stringProp>\n              </ConstantTimer>\n              <hashTree/>\n              <GaussianRandomTimer guiclass=\"GaussianRandomTimerGui\" testclass=\"GaussianRandomTimer\" testname=\"Gaussian Random Timer\" enabled=\"true\">\n                <stringProp name=\"ConstantTimer.delay\">5000</stringProp>\n                <stringProp name=\"RandomTimer.range\">0</stringProp>\n              </GaussianRandomTimer>\n              <hashTree/>\n              <ResultCollector guiclass=\"ViewResultsFullVisualizer\" testclass=\"ResultCollector\" testname=\"View Results Tree\" enabled=\"false\">\n                <boolProp name=\"ResultCollector.error_logging\">false</boolProp>\n                <objProp>\n                  <name>saveConfig</name>\n                  <value class=\"SampleSaveConfiguration\">\n                    <time>true</time>\n                    <latency>true</latency>\n                    <timestamp>true</timestamp>\n                    <success>true</success>\n                    <label>true</label>\n                    <code>true</code>\n                    <message>true</message>\n                    <threadName>true</threadName>\n                    <dataType>true</dataType>\n                    <encoding>false</encoding>\n                    <assertions>true</assertions>\n                    <subresults>true</subresults>\n                    <responseData>false</responseData>\n                    <samplerData>false</samplerData>\n                    <xml>false</xml>\n                    <fieldNames>true</fieldNames>\n                    <responseHeaders>false</responseHeaders>\n                    <requestHeaders>false</requestHeaders>\n                    <responseDataOnError>false</responseDataOnError>\n                    <saveAssertionResultsFailureMessage>true</saveAssertionResultsFailureMessage>\n                    <assertionsResultsToSave>0</assertionsResultsToSave>\n                    <bytes>true</bytes>\n                    <sentBytes>true</sentBytes>\n                    <threadCounts>true</threadCounts>\n                    <idleTime>true</idleTime>\n                    <connectTime>true</connectTime>\n                  </value>\n                </objProp>\n                <stringProp name=\"filename\"></stringProp>\n              </ResultCollector>\n              <hashTree/>\n            </hashTree>\n            <LoopController guiclass=\"LoopControlPanel\" testclass=\"LoopController\" testname=\"Loop x5 - View one of my courses, and a resource\" enabled=\"true\">\n              <boolProp name=\"LoopController.continue_forever\">true</boolProp>\n              <stringProp name=\"LoopController.loops\">5</stringProp>\n            </LoopController>\n            <hashTree>\n              <UserParameters guiclass=\"UserParametersGui\" testclass=\"UserParameters\" testname=\"User Parameters\" enabled=\"true\">\n                <collectionProp name=\"UserParameters.names\">\n                  <stringProp name=\"-852875554\">RandomCourse</stringProp>\n                  <stringProp name=\"-368356746\">courseid</stringProp>\n                </collectionProp>\n                <collectionProp name=\"UserParameters.thread_values\">\n                  <collectionProp name=\"-883654058\">\n                    <stringProp name=\"1948420855\">${__Random(1,${course_id_n},ReuseRandomNumber)}</stringProp>\n                    <stringProp name=\"-1361716841\">${activity_course_id}</stringProp>\n                  </collectionProp>\n                </collectionProp>\n                <boolProp name=\"UserParameters.per_iteration\">true</boolProp>\n              </UserParameters>\n              <hashTree/>\n              <HTTPSamplerProxy guiclass=\"HttpTestSampleGui\" testclass=\"HTTPSamplerProxy\" testname=\"Go to course\" enabled=\"true\">\n                <elementProp name=\"HTTPsampler.Arguments\" elementType=\"Arguments\" guiclass=\"HTTPArgumentsPanel\" testclass=\"Arguments\" testname=\"Argument List\" enabled=\"true\">\n                  <collectionProp name=\"Arguments.arguments\">\n                    <elementProp name=\"id\" elementType=\"HTTPArgument\">\n                      <boolProp name=\"HTTPArgument.always_encode\">false</boolProp>\n                      <stringProp name=\"Argument.value\">${courseid}</stringProp>\n                      <stringProp name=\"Argument.name\">id</stringProp>\n                      <stringProp name=\"Argument.metadata\">=</stringProp>\n                      <stringProp name=\"Argument.use_equals\">true</stringProp>\n                      <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                    </elementProp>\n                  </collectionProp>\n                </elementProp>\n                <stringProp name=\"HTTPSampler.domain\">${host}</stringProp>\n                <stringProp name=\"HTTPSampler.port\">${port}</stringProp>\n                <stringProp name=\"HTTPSampler.protocol\">${protocol}</stringProp>\n                <stringProp name=\"HTTPSampler.contentEncoding\"></stringProp>\n                <stringProp name=\"HTTPSampler.path\">/course/view.php</stringProp>\n                <stringProp name=\"HTTPSampler.method\">GET</stringProp>\n                <boolProp name=\"HTTPSampler.follow_redirects\">true</boolProp>\n                <boolProp name=\"HTTPSampler.auto_redirects\">false</boolProp>\n                <boolProp name=\"HTTPSampler.use_keepalive\">true</boolProp>\n                <boolProp name=\"HTTPSampler.DO_MULTIPART_POST\">false</boolProp>\n                <stringProp name=\"HTTPSampler.embedded_url_re\"></stringProp>\n                <stringProp name=\"HTTPSampler.implementation\">Java</stringProp>\n                <stringProp name=\"HTTPSampler.connect_timeout\">${default_connect_timeout}</stringProp>\n                <stringProp name=\"HTTPSampler.response_timeout\">${default_response_timeout}</stringProp>\n              </HTTPSamplerProxy>\n              <hashTree>\n                <ConstantTimer guiclass=\"ConstantTimerGui\" testclass=\"ConstantTimer\" testname=\"Constant Timer\" enabled=\"false\">\n                  <stringProp name=\"ConstantTimer.delay\">1000</stringProp>\n                </ConstantTimer>\n                <hashTree/>\n                <GaussianRandomTimer guiclass=\"GaussianRandomTimerGui\" testclass=\"GaussianRandomTimer\" testname=\"Gaussian Random Timer\" enabled=\"true\">\n                  <stringProp name=\"ConstantTimer.delay\">10000</stringProp>\n                  <stringProp name=\"RandomTimer.range\">25000</stringProp>\n                </GaussianRandomTimer>\n                <hashTree/>\n                <RegexExtractor guiclass=\"RegexExtractorGui\" testclass=\"RegexExtractor\" testname=\"RegExp - Get random resource_module_id\" enabled=\"true\">\n                  <stringProp name=\"RegexExtractor.useHeaders\">false</stringProp>\n                  <stringProp name=\"RegexExtractor.refname\">resource_module_id</stringProp>\n                  <stringProp name=\"RegexExtractor.regex\">\\/mod\\/resource\\/view.php\\?id=(\\d+)&quot;</stringProp>\n                  <stringProp name=\"RegexExtractor.template\">$1$</stringProp>\n                  <stringProp name=\"RegexExtractor.default\">NULL</stringProp>\n                  <stringProp name=\"RegexExtractor.match_number\">0</stringProp>\n                </RegexExtractor>\n                <hashTree/>\n              </hashTree>\n              <IfController guiclass=\"IfControllerPanel\" testclass=\"IfController\" testname=\"Resource View (30% frequency the time)\" enabled=\"true\">\n                <stringProp name=\"IfController.condition\">${__Random(1,10,ReuseRandomNumber)} &lt;= 10</stringProp>\n                <boolProp name=\"IfController.evaluateAll\">false</boolProp>\n              </IfController>\n              <hashTree>\n                <IfController guiclass=\"IfControllerPanel\" testclass=\"IfController\" testname=\"View resource IF there is one\" enabled=\"true\">\n                  <stringProp name=\"IfController.condition\">&quot;${resource_module_id}&quot; != &quot;NULL&quot;</stringProp>\n                  <boolProp name=\"IfController.evaluateAll\">false</boolProp>\n                </IfController>\n                <hashTree>\n                  <HTTPSamplerProxy guiclass=\"HttpTestSampleGui\" testclass=\"HTTPSamplerProxy\" testname=\"Open the resource page\" enabled=\"true\">\n                    <elementProp name=\"HTTPsampler.Arguments\" elementType=\"Arguments\" guiclass=\"HTTPArgumentsPanel\" testclass=\"Arguments\" testname=\"Argument List\" enabled=\"true\">\n                      <collectionProp name=\"Arguments.arguments\">\n                        <elementProp name=\"id\" elementType=\"HTTPArgument\">\n                          <boolProp name=\"HTTPArgument.always_encode\">false</boolProp>\n                          <stringProp name=\"Argument.value\">${resource_module_id}</stringProp>\n                          <stringProp name=\"Argument.name\">id</stringProp>\n                          <stringProp name=\"Argument.metadata\">=</stringProp>\n                          <stringProp name=\"Argument.use_equals\">true</stringProp>\n                          <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                        </elementProp>\n                      </collectionProp>\n                    </elementProp>\n                    <stringProp name=\"HTTPSampler.domain\">${host}</stringProp>\n                    <stringProp name=\"HTTPSampler.port\">${port}</stringProp>\n                    <stringProp name=\"HTTPSampler.protocol\">${protocol}</stringProp>\n                    <stringProp name=\"HTTPSampler.contentEncoding\"></stringProp>\n                    <stringProp name=\"HTTPSampler.path\">/mod/resource/view.php</stringProp>\n                    <stringProp name=\"HTTPSampler.method\">GET</stringProp>\n                    <boolProp name=\"HTTPSampler.follow_redirects\">true</boolProp>\n                    <boolProp name=\"HTTPSampler.auto_redirects\">false</boolProp>\n                    <boolProp name=\"HTTPSampler.use_keepalive\">true</boolProp>\n                    <boolProp name=\"HTTPSampler.DO_MULTIPART_POST\">false</boolProp>\n                    <stringProp name=\"HTTPSampler.embedded_url_re\"></stringProp>\n                    <stringProp name=\"HTTPSampler.implementation\">Java</stringProp>\n                    <stringProp name=\"HTTPSampler.connect_timeout\"></stringProp>\n                    <stringProp name=\"HTTPSampler.response_timeout\"></stringProp>\n                  </HTTPSamplerProxy>\n                  <hashTree>\n                    <ConstantTimer guiclass=\"ConstantTimerGui\" testclass=\"ConstantTimer\" testname=\"Constant Timer\" enabled=\"false\">\n                      <stringProp name=\"ConstantTimer.delay\">1000</stringProp>\n                    </ConstantTimer>\n                    <hashTree/>\n                    <GaussianRandomTimer guiclass=\"GaussianRandomTimerGui\" testclass=\"GaussianRandomTimer\" testname=\"Gaussian Random Timer\" enabled=\"true\">\n                      <stringProp name=\"ConstantTimer.delay\">3000</stringProp>\n                      <stringProp name=\"RandomTimer.range\">5000</stringProp>\n                    </GaussianRandomTimer>\n                    <hashTree/>\n                  </hashTree>\n                </hashTree>\n              </hashTree>\n              <DebugSampler guiclass=\"TestBeanGUI\" testclass=\"DebugSampler\" testname=\"Debug Sampler\" enabled=\"false\">\n                <boolProp name=\"displayJMeterProperties\">false</boolProp>\n                <boolProp name=\"displayJMeterVariables\">true</boolProp>\n                <boolProp name=\"displaySystemProperties\">false</boolProp>\n              </DebugSampler>\n              <hashTree/>\n              <ResultCollector guiclass=\"ViewResultsFullVisualizer\" testclass=\"ResultCollector\" testname=\"View Results Tree\" enabled=\"false\">\n                <boolProp name=\"ResultCollector.error_logging\">false</boolProp>\n                <objProp>\n                  <name>saveConfig</name>\n                  <value class=\"SampleSaveConfiguration\">\n                    <time>true</time>\n                    <latency>true</latency>\n                    <timestamp>true</timestamp>\n                    <success>true</success>\n                    <label>true</label>\n                    <code>true</code>\n                    <message>true</message>\n                    <threadName>true</threadName>\n                    <dataType>true</dataType>\n                    <encoding>false</encoding>\n                    <assertions>true</assertions>\n                    <subresults>true</subresults>\n                    <responseData>false</responseData>\n                    <samplerData>false</samplerData>\n                    <xml>false</xml>\n                    <fieldNames>false</fieldNames>\n                    <responseHeaders>false</responseHeaders>\n                    <requestHeaders>false</requestHeaders>\n                    <responseDataOnError>false</responseDataOnError>\n                    <saveAssertionResultsFailureMessage>false</saveAssertionResultsFailureMessage>\n                    <assertionsResultsToSave>0</assertionsResultsToSave>\n                    <bytes>true</bytes>\n                    <threadCounts>true</threadCounts>\n                  </value>\n                </objProp>\n                <stringProp name=\"filename\"></stringProp>\n              </ResultCollector>\n              <hashTree/>\n            </hashTree>\n            <LoopController guiclass=\"LoopControlPanel\" testclass=\"LoopController\" testname=\"Loop x3\" enabled=\"true\">\n              <boolProp name=\"LoopController.continue_forever\">true</boolProp>\n              <stringProp name=\"LoopController.loops\">3</stringProp>\n            </LoopController>\n            <hashTree>\n              <IfController guiclass=\"IfControllerPanel\" testclass=\"IfController\" testname=\"View forum (20% of the time)\" enabled=\"true\">\n                <stringProp name=\"TestPlan.comments\">20% of the time</stringProp>\n                <stringProp name=\"IfController.condition\">${__Random(1,10,ReuseRandomNumber)} &lt;= 10</stringProp>\n                <boolProp name=\"IfController.evaluateAll\">false</boolProp>\n              </IfController>\n              <hashTree>\n                <HTTPSamplerProxy guiclass=\"HttpTestSampleGui\" testclass=\"HTTPSamplerProxy\" testname=\"Goto course\" enabled=\"true\">\n                  <elementProp name=\"HTTPsampler.Arguments\" elementType=\"Arguments\" guiclass=\"HTTPArgumentsPanel\" testclass=\"Arguments\" testname=\"Argument List\" enabled=\"true\">\n                    <collectionProp name=\"Arguments.arguments\">\n                      <elementProp name=\"id\" elementType=\"HTTPArgument\">\n                        <boolProp name=\"HTTPArgument.always_encode\">false</boolProp>\n                        <stringProp name=\"Argument.value\">${activity_course_id}</stringProp>\n                        <stringProp name=\"Argument.name\">id</stringProp>\n                        <stringProp name=\"Argument.metadata\">=</stringProp>\n                        <stringProp name=\"Argument.use_equals\">true</stringProp>\n                        <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                      </elementProp>\n                    </collectionProp>\n                  </elementProp>\n                  <stringProp name=\"HTTPSampler.domain\">${host}</stringProp>\n                  <stringProp name=\"HTTPSampler.port\">${port}</stringProp>\n                  <stringProp name=\"HTTPSampler.protocol\">${protocol}</stringProp>\n                  <stringProp name=\"HTTPSampler.contentEncoding\"></stringProp>\n                  <stringProp name=\"HTTPSampler.path\">/course/view.php</stringProp>\n                  <stringProp name=\"HTTPSampler.method\">GET</stringProp>\n                  <boolProp name=\"HTTPSampler.follow_redirects\">true</boolProp>\n                  <boolProp name=\"HTTPSampler.auto_redirects\">false</boolProp>\n                  <boolProp name=\"HTTPSampler.use_keepalive\">true</boolProp>\n                  <boolProp name=\"HTTPSampler.DO_MULTIPART_POST\">false</boolProp>\n                  <stringProp name=\"HTTPSampler.embedded_url_re\"></stringProp>\n                  <stringProp name=\"HTTPSampler.implementation\">Java</stringProp>\n                  <stringProp name=\"HTTPSampler.connect_timeout\"></stringProp>\n                  <stringProp name=\"HTTPSampler.response_timeout\"></stringProp>\n                </HTTPSamplerProxy>\n                <hashTree>\n                  <ResponseAssertion guiclass=\"AssertionGui\" testclass=\"ResponseAssertion\" testname=\"Assertion - in the course?\" enabled=\"true\">\n                    <collectionProp name=\"Asserion.test_strings\">\n                      <stringProp name=\"1915803329\">Performance Testing Forum</stringProp>\n                    </collectionProp>\n                    <intProp name=\"Assertion.test_type\">2</intProp>\n                    <stringProp name=\"Assertion.test_field\">Assertion.response_data</stringProp>\n                    <boolProp name=\"Assertion.assume_success\">false</boolProp>\n                    <stringProp name=\"Assertion.custom_message\"></stringProp>\n                  </ResponseAssertion>\n                  <hashTree/>\n                  <RegexExtractor guiclass=\"RegexExtractorGui\" testclass=\"RegexExtractor\" testname=\"RegExp Extractor - Get coursetopics_html\" enabled=\"true\">\n                    <stringProp name=\"RegexExtractor.useHeaders\">false</stringProp>\n                    <stringProp name=\"RegexExtractor.refname\">coursetopics_html</stringProp>\n                    <stringProp name=\"RegexExtractor.regex\">Performance Testing Peak Quiz.*(&lt;div&gt;&lt;div class=&quot;mod-indent-outer&quot;&gt;.*?&lt;a .*Performance Testing Forum 0.*Performance Testing Forum 9.*?&lt;\\/a&gt;)</stringProp>\n                    <stringProp name=\"RegexExtractor.template\">$1$</stringProp>\n                    <stringProp name=\"RegexExtractor.default\">NULL</stringProp>\n                    <stringProp name=\"RegexExtractor.match_number\"></stringProp>\n                  </RegexExtractor>\n                  <hashTree/>\n                  <XPathExtractor guiclass=\"XPathExtractorGui\" testclass=\"XPathExtractor\" testname=\"XPath Extractor - Get Random Performance Forum Module id\" enabled=\"true\">\n                    <stringProp name=\"XPathExtractor.default\">NULL</stringProp>\n                    <stringProp name=\"XPathExtractor.refname\">moduleid</stringProp>\n                    <stringProp name=\"XPathExtractor.xpathQuery\">substring-after(//*[contains(text(),&apos;Performance Testing Forum ${__Random(0,9,ReuseRandomNumber)}&apos;)]/../@href,&apos;id=&apos;)</stringProp>\n                    <boolProp name=\"XPathExtractor.validate\">false</boolProp>\n                    <boolProp name=\"XPathExtractor.tolerant\">true</boolProp>\n                    <boolProp name=\"XPathExtractor.namespace\">false</boolProp>\n                    <boolProp name=\"XPathExtractor.show_warnings\">true</boolProp>\n                    <boolProp name=\"XPathExtractor.quiet\">false</boolProp>\n                    <stringProp name=\"TestPlan.comments\">Pick one of the 10 Random forums, we use 10 to reduce the index impact of one becoming large and exploding the query time to view the forum</stringProp>\n                    <boolProp name=\"XPathExtractor.report_errors\">true</boolProp>\n                    <stringProp name=\"Sample.scope\">variable</stringProp>\n                    <stringProp name=\"Scope.variable\">coursetopics_html</stringProp>\n                    <stringProp name=\"XPathExtractor.matchNumber\">-1</stringProp>\n                  </XPathExtractor>\n                  <hashTree/>\n                  <XPathExtractor guiclass=\"XPathExtractorGui\" testclass=\"XPathExtractor\" testname=\"XPath Extractor - Get module id\" enabled=\"false\">\n                    <stringProp name=\"XPathExtractor.default\">NULL</stringProp>\n                    <stringProp name=\"XPathExtractor.refname\">moduleid</stringProp>\n                    <stringProp name=\"XPathExtractor.xpathQuery\">substring-after(//*/div[@class=&quot;activityinstance&quot;]/*/*[contains(text(),&apos;Performance Testing Forum ${__Random(0,9,ReuseRandomNumber)}&apos;)]/../@href,&apos;id=&apos;)</stringProp>\n                    <boolProp name=\"XPathExtractor.validate\">false</boolProp>\n                    <boolProp name=\"XPathExtractor.tolerant\">true</boolProp>\n                    <boolProp name=\"XPathExtractor.namespace\">false</boolProp>\n                    <boolProp name=\"XPathExtractor.show_warnings\">true</boolProp>\n                    <boolProp name=\"XPathExtractor.quiet\">false</boolProp>\n                    <stringProp name=\"TestPlan.comments\">Pick one of the 10 Random forums, we use 10 to reduce the index impact of one becoming large and exploding the query time to view the forum</stringProp>\n                    <boolProp name=\"XPathExtractor.report_errors\">true</boolProp>\n                    <stringProp name=\"XPathExtractor.matchNumber\">-1</stringProp>\n                  </XPathExtractor>\n                  <hashTree/>\n                  <ConstantTimer guiclass=\"ConstantTimerGui\" testclass=\"ConstantTimer\" testname=\"Constant Timer\" enabled=\"false\">\n                    <stringProp name=\"ConstantTimer.delay\">1000</stringProp>\n                  </ConstantTimer>\n                  <hashTree/>\n                  <GaussianRandomTimer guiclass=\"GaussianRandomTimerGui\" testclass=\"GaussianRandomTimer\" testname=\"Gaussian Random Timer\" enabled=\"true\">\n                    <stringProp name=\"ConstantTimer.delay\">10000</stringProp>\n                    <stringProp name=\"RandomTimer.range\">30000</stringProp>\n                  </GaussianRandomTimer>\n                  <hashTree/>\n                </hashTree>\n                <HTTPSamplerProxy guiclass=\"HttpTestSampleGui\" testclass=\"HTTPSamplerProxy\" testname=\"Open Forum Module\" enabled=\"true\">\n                  <elementProp name=\"HTTPsampler.Arguments\" elementType=\"Arguments\" guiclass=\"HTTPArgumentsPanel\" testclass=\"Arguments\" testname=\"Argument List\" enabled=\"true\">\n                    <collectionProp name=\"Arguments.arguments\">\n                      <elementProp name=\"id\" elementType=\"HTTPArgument\">\n                        <boolProp name=\"HTTPArgument.always_encode\">false</boolProp>\n                        <stringProp name=\"Argument.value\">${moduleid}</stringProp>\n                        <stringProp name=\"Argument.name\">id</stringProp>\n                        <stringProp name=\"Argument.metadata\">=</stringProp>\n                        <stringProp name=\"Argument.use_equals\">true</stringProp>\n                        <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                      </elementProp>\n                    </collectionProp>\n                  </elementProp>\n                  <stringProp name=\"HTTPSampler.domain\">${host}</stringProp>\n                  <stringProp name=\"HTTPSampler.port\">${port}</stringProp>\n                  <stringProp name=\"HTTPSampler.protocol\">${protocol}</stringProp>\n                  <stringProp name=\"HTTPSampler.contentEncoding\"></stringProp>\n                  <stringProp name=\"HTTPSampler.path\">/mod/forum/view.php</stringProp>\n                  <stringProp name=\"HTTPSampler.method\">GET</stringProp>\n                  <boolProp name=\"HTTPSampler.follow_redirects\">true</boolProp>\n                  <boolProp name=\"HTTPSampler.auto_redirects\">false</boolProp>\n                  <boolProp name=\"HTTPSampler.use_keepalive\">true</boolProp>\n                  <boolProp name=\"HTTPSampler.DO_MULTIPART_POST\">false</boolProp>\n                  <stringProp name=\"HTTPSampler.embedded_url_re\"></stringProp>\n                  <stringProp name=\"HTTPSampler.implementation\">Java</stringProp>\n                  <stringProp name=\"HTTPSampler.connect_timeout\"></stringProp>\n                  <stringProp name=\"HTTPSampler.response_timeout\"></stringProp>\n                </HTTPSamplerProxy>\n                <hashTree>\n                  <ResponseAssertion guiclass=\"AssertionGui\" testclass=\"ResponseAssertion\" testname=\"Assertion - in the forum module ?\" enabled=\"true\">\n                    <collectionProp name=\"Asserion.test_strings\">\n                      <stringProp name=\"1915803329\">Performance Testing Forum</stringProp>\n                      <stringProp name=\"544764565\">Add a new discussion topic</stringProp>\n                    </collectionProp>\n                    <stringProp name=\"Assertion.test_field\">Assertion.response_data</stringProp>\n                    <boolProp name=\"Assertion.assume_success\">false</boolProp>\n                    <intProp name=\"Assertion.test_type\">2</intProp>\n                    <stringProp name=\"Assertion.custom_message\"></stringProp>\n                  </ResponseAssertion>\n                  <hashTree/>\n                  <RegexExtractor guiclass=\"RegexExtractorGui\" testclass=\"RegexExtractor\" testname=\"RegExp Extractor - Get forum discussion headings (if any!)\" enabled=\"true\">\n                    <stringProp name=\"RegexExtractor.useHeaders\">false</stringProp>\n                    <stringProp name=\"RegexExtractor.refname\">forumheaderlist</stringProp>\n                    <stringProp name=\"RegexExtractor.regex\">(?s)(&lt;table .*?class=&quot;forumheaderlist&quot;.*?&lt;\\/table&gt;)</stringProp>\n                    <stringProp name=\"RegexExtractor.template\">$1$</stringProp>\n                    <stringProp name=\"RegexExtractor.default\">NULL</stringProp>\n                    <stringProp name=\"RegexExtractor.match_number\"></stringProp>\n                  </RegexExtractor>\n                  <hashTree/>\n                  <XPathExtractor guiclass=\"XPathExtractorGui\" testclass=\"XPathExtractor\" testname=\"XPath Extractor - Get random discussion id (if any)\" enabled=\"true\">\n                    <stringProp name=\"XPathExtractor.default\">NULL</stringProp>\n                    <stringProp name=\"XPathExtractor.refname\">discuss_id</stringProp>\n                    <stringProp name=\"XPathExtractor.xpathQuery\">substring-after(//*/div[@class=&quot;activityinstance&quot;]/*/*[contains(text(),&apos;Performance Testing Forum ${__Random(0,9,ReuseRandomNumber)}&apos;)]/../@href,&apos;id=&apos;)</stringProp>\n                    <boolProp name=\"XPathExtractor.validate\">false</boolProp>\n                    <boolProp name=\"XPathExtractor.tolerant\">true</boolProp>\n                    <boolProp name=\"XPathExtractor.namespace\">false</boolProp>\n                    <boolProp name=\"XPathExtractor.show_warnings\">true</boolProp>\n                    <boolProp name=\"XPathExtractor.quiet\">false</boolProp>\n                    <stringProp name=\"TestPlan.comments\">Pick one of the 10 Random forums, we use 10 to reduce the index impact of one becoming large and exploding the query time to view the forum</stringProp>\n                    <boolProp name=\"XPathExtractor.report_errors\">true</boolProp>\n                    <stringProp name=\"Sample.scope\">variable</stringProp>\n                    <stringProp name=\"Scope.variable\">forumheaderlist</stringProp>\n                    <stringProp name=\"XPathExtractor.matchNumber\">-1</stringProp>\n                  </XPathExtractor>\n                  <hashTree/>\n                  <RegexExtractor guiclass=\"RegexExtractorGui\" testclass=\"RegexExtractor\" testname=\"RegExp Extractor - Get Forum id\" enabled=\"true\">\n                    <stringProp name=\"RegexExtractor.useHeaders\">false</stringProp>\n                    <stringProp name=\"RegexExtractor.refname\">forumid</stringProp>\n                    <stringProp name=\"RegexExtractor.regex\">name=&quot;forum&quot; value=&quot;(\\d+)&quot;</stringProp>\n                    <stringProp name=\"RegexExtractor.template\">$1$</stringProp>\n                    <stringProp name=\"RegexExtractor.default\">NULL</stringProp>\n                    <stringProp name=\"RegexExtractor.match_number\"></stringProp>\n                  </RegexExtractor>\n                  <hashTree/>\n                  <XPathExtractor guiclass=\"XPathExtractorGui\" testclass=\"XPathExtractor\" testname=\"Get forum id\" enabled=\"false\">\n                    <stringProp name=\"XPathExtractor.default\"></stringProp>\n                    <stringProp name=\"XPathExtractor.refname\">forumid</stringProp>\n                    <stringProp name=\"XPathExtractor.xpathQuery\">//*[@name=&apos;forum&apos;][1]/@value</stringProp>\n                    <boolProp name=\"XPathExtractor.validate\">false</boolProp>\n                    <boolProp name=\"XPathExtractor.tolerant\">true</boolProp>\n                    <boolProp name=\"XPathExtractor.namespace\">false</boolProp>\n                    <boolProp name=\"XPathExtractor.show_warnings\">true</boolProp>\n                    <boolProp name=\"XPathExtractor.report_errors\">true</boolProp>\n                    <boolProp name=\"XPathExtractor.quiet\">false</boolProp>\n                    <stringProp name=\"XPathExtractor.matchNumber\">-1</stringProp>\n                  </XPathExtractor>\n                  <hashTree/>\n                  <RegexExtractor guiclass=\"RegexExtractorGui\" testclass=\"RegexExtractor\" testname=\"RegExp Extractor - Get random discussion id\" enabled=\"true\">\n                    <stringProp name=\"RegexExtractor.useHeaders\">false</stringProp>\n                    <stringProp name=\"RegexExtractor.refname\">discuss_id</stringProp>\n                    <stringProp name=\"RegexExtractor.regex\">\\/mod\\/forum\\/discuss.php\\?d=(\\d+)</stringProp>\n                    <stringProp name=\"RegexExtractor.template\">$1$</stringProp>\n                    <stringProp name=\"RegexExtractor.default\">NULL</stringProp>\n                    <stringProp name=\"RegexExtractor.match_number\">0</stringProp>\n                  </RegexExtractor>\n                  <hashTree/>\n                  <RegexExtractor guiclass=\"RegexExtractorGui\" testclass=\"RegexExtractor\" testname=\"RegExp Extractor - Get corresponding forum discussion title\" enabled=\"true\">\n                    <stringProp name=\"RegexExtractor.useHeaders\">false</stringProp>\n                    <stringProp name=\"RegexExtractor.refname\">discuss_title</stringProp>\n                    <stringProp name=\"RegexExtractor.regex\">\\/mod\\/forum\\/discuss.php\\?d=${discuss_id}&quot;&gt;(.*?)&lt;/a&gt;</stringProp>\n                    <stringProp name=\"RegexExtractor.template\">$1$</stringProp>\n                    <stringProp name=\"RegexExtractor.default\">NULL</stringProp>\n                    <stringProp name=\"RegexExtractor.match_number\">1</stringProp>\n                  </RegexExtractor>\n                  <hashTree/>\n                  <ConstantTimer guiclass=\"ConstantTimerGui\" testclass=\"ConstantTimer\" testname=\"Constant Timer\" enabled=\"false\">\n                    <stringProp name=\"ConstantTimer.delay\">1000</stringProp>\n                  </ConstantTimer>\n                  <hashTree/>\n                  <GaussianRandomTimer guiclass=\"GaussianRandomTimerGui\" testclass=\"GaussianRandomTimer\" testname=\"Gaussian Random Timer\" enabled=\"true\">\n                    <stringProp name=\"ConstantTimer.delay\">25000</stringProp>\n                    <stringProp name=\"RandomTimer.range\">20000</stringProp>\n                  </GaussianRandomTimer>\n                  <hashTree/>\n                </hashTree>\n                <DebugSampler guiclass=\"TestBeanGUI\" testclass=\"DebugSampler\" testname=\"Debug Sampler\" enabled=\"false\">\n                  <boolProp name=\"displayJMeterProperties\">false</boolProp>\n                  <boolProp name=\"displayJMeterVariables\">true</boolProp>\n                  <boolProp name=\"displaySystemProperties\">false</boolProp>\n                </DebugSampler>\n                <hashTree/>\n                <ResultCollector guiclass=\"ViewResultsFullVisualizer\" testclass=\"ResultCollector\" testname=\"View Results Tree\" enabled=\"false\">\n                  <boolProp name=\"ResultCollector.error_logging\">false</boolProp>\n                  <objProp>\n                    <name>saveConfig</name>\n                    <value class=\"SampleSaveConfiguration\">\n                      <time>true</time>\n                      <latency>true</latency>\n                      <timestamp>true</timestamp>\n                      <success>true</success>\n                      <label>true</label>\n                      <code>true</code>\n                      <message>true</message>\n                      <threadName>true</threadName>\n                      <dataType>true</dataType>\n                      <encoding>false</encoding>\n                      <assertions>true</assertions>\n                      <subresults>true</subresults>\n                      <responseData>false</responseData>\n                      <samplerData>false</samplerData>\n                      <xml>false</xml>\n                      <fieldNames>false</fieldNames>\n                      <responseHeaders>false</responseHeaders>\n                      <requestHeaders>false</requestHeaders>\n                      <responseDataOnError>false</responseDataOnError>\n                      <saveAssertionResultsFailureMessage>false</saveAssertionResultsFailureMessage>\n                      <assertionsResultsToSave>0</assertionsResultsToSave>\n                      <bytes>true</bytes>\n                      <threadCounts>true</threadCounts>\n                    </value>\n                  </objProp>\n                  <stringProp name=\"filename\"></stringProp>\n                </ResultCollector>\n                <hashTree/>\n                <IfController guiclass=\"IfControllerPanel\" testclass=\"IfController\" testname=\"If a Forum Discussion Exists...\" enabled=\"true\">\n                  <stringProp name=\"IfController.condition\">&quot;${discuss_id}&quot; != &quot;NULL&quot; &amp;&amp; ${discuss_id} &gt; 0</stringProp>\n                  <boolProp name=\"IfController.evaluateAll\">false</boolProp>\n                </IfController>\n                <hashTree>\n                  <IfController guiclass=\"IfControllerPanel\" testclass=\"IfController\" testname=\"Forum View Discussion (50% of the time)\" enabled=\"true\">\n                    <stringProp name=\"IfController.condition\">${__Random(1,10,ReuseRandomNumber)} &lt;= 10</stringProp>\n                    <boolProp name=\"IfController.evaluateAll\">false</boolProp>\n                  </IfController>\n                  <hashTree>\n                    <HTTPSamplerProxy guiclass=\"HttpTestSampleGui\" testclass=\"HTTPSamplerProxy\" testname=\"Forum View Discussion\" enabled=\"true\">\n                      <elementProp name=\"HTTPsampler.Arguments\" elementType=\"Arguments\" guiclass=\"HTTPArgumentsPanel\" testclass=\"Arguments\" testname=\"Argument List\" enabled=\"true\">\n                        <collectionProp name=\"Arguments.arguments\">\n                          <elementProp name=\"d\" elementType=\"HTTPArgument\">\n                            <boolProp name=\"HTTPArgument.always_encode\">false</boolProp>\n                            <stringProp name=\"Argument.value\">${discuss_id}</stringProp>\n                            <stringProp name=\"Argument.name\">d</stringProp>\n                            <stringProp name=\"Argument.metadata\">=</stringProp>\n                            <stringProp name=\"Argument.use_equals\">true</stringProp>\n                            <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                          </elementProp>\n                        </collectionProp>\n                      </elementProp>\n                      <stringProp name=\"HTTPSampler.domain\">${host}</stringProp>\n                      <stringProp name=\"HTTPSampler.port\">${port}</stringProp>\n                      <stringProp name=\"HTTPSampler.protocol\">${protocol}</stringProp>\n                      <stringProp name=\"HTTPSampler.contentEncoding\"></stringProp>\n                      <stringProp name=\"HTTPSampler.path\">/mod/forum/discuss.php</stringProp>\n                      <stringProp name=\"HTTPSampler.method\">GET</stringProp>\n                      <boolProp name=\"HTTPSampler.follow_redirects\">true</boolProp>\n                      <boolProp name=\"HTTPSampler.auto_redirects\">false</boolProp>\n                      <boolProp name=\"HTTPSampler.use_keepalive\">true</boolProp>\n                      <boolProp name=\"HTTPSampler.DO_MULTIPART_POST\">false</boolProp>\n                      <stringProp name=\"HTTPSampler.embedded_url_re\"></stringProp>\n                      <stringProp name=\"HTTPSampler.implementation\">Java</stringProp>\n                      <stringProp name=\"HTTPSampler.connect_timeout\"></stringProp>\n                      <stringProp name=\"HTTPSampler.response_timeout\"></stringProp>\n                      <stringProp name=\"TestPlan.comments\">View a forum discussion thread</stringProp>\n                    </HTTPSamplerProxy>\n                    <hashTree>\n                      <ResponseAssertion guiclass=\"AssertionGui\" testclass=\"ResponseAssertion\" testname=\"Assertion - in the discussion thread ?\" enabled=\"true\">\n                        <collectionProp name=\"Asserion.test_strings\">\n                          <stringProp name=\"1415837435\">${discuss_title}</stringProp>\n                          <stringProp name=\"-1185574437\">My Course list is</stringProp>\n                        </collectionProp>\n                        <stringProp name=\"Assertion.test_field\">Assertion.response_data</stringProp>\n                        <boolProp name=\"Assertion.assume_success\">false</boolProp>\n                        <intProp name=\"Assertion.test_type\">2</intProp>\n                        <stringProp name=\"Assertion.custom_message\"></stringProp>\n                      </ResponseAssertion>\n                      <hashTree/>\n                      <RegexExtractor guiclass=\"RegexExtractorGui\" testclass=\"RegexExtractor\" testname=\"RegExp Extractor - Get Forum id\" enabled=\"false\">\n                        <stringProp name=\"RegexExtractor.useHeaders\">false</stringProp>\n                        <stringProp name=\"RegexExtractor.refname\">forumid</stringProp>\n                        <stringProp name=\"RegexExtractor.regex\">name=&quot;forum&quot; value=&quot;(\\d+)&quot;</stringProp>\n                        <stringProp name=\"RegexExtractor.template\">$1$</stringProp>\n                        <stringProp name=\"RegexExtractor.default\">NULL</stringProp>\n                        <stringProp name=\"RegexExtractor.match_number\"></stringProp>\n                      </RegexExtractor>\n                      <hashTree/>\n                      <XPathExtractor guiclass=\"XPathExtractorGui\" testclass=\"XPathExtractor\" testname=\"XPath Extractor - Get forum id\" enabled=\"false\">\n                        <stringProp name=\"XPathExtractor.default\"></stringProp>\n                        <stringProp name=\"XPathExtractor.refname\">forumid</stringProp>\n                        <stringProp name=\"XPathExtractor.xpathQuery\">//*[@name=&apos;forum&apos;][1]/@value</stringProp>\n                        <boolProp name=\"XPathExtractor.validate\">false</boolProp>\n                        <boolProp name=\"XPathExtractor.tolerant\">true</boolProp>\n                        <boolProp name=\"XPathExtractor.namespace\">false</boolProp>\n                        <boolProp name=\"XPathExtractor.show_warnings\">true</boolProp>\n                        <boolProp name=\"XPathExtractor.report_errors\">true</boolProp>\n                        <boolProp name=\"XPathExtractor.quiet\">false</boolProp>\n                        <stringProp name=\"XPathExtractor.matchNumber\">-1</stringProp>\n                      </XPathExtractor>\n                      <hashTree/>\n                      <ConstantTimer guiclass=\"ConstantTimerGui\" testclass=\"ConstantTimer\" testname=\"Constant Timer\" enabled=\"false\">\n                        <stringProp name=\"ConstantTimer.delay\">1000</stringProp>\n                      </ConstantTimer>\n                      <hashTree/>\n                      <GaussianRandomTimer guiclass=\"GaussianRandomTimerGui\" testclass=\"GaussianRandomTimer\" testname=\"Gaussian Random Timer\" enabled=\"true\">\n                        <stringProp name=\"ConstantTimer.delay\">3000</stringProp>\n                        <stringProp name=\"RandomTimer.range\">12000</stringProp>\n                      </GaussianRandomTimer>\n                      <hashTree/>\n                    </hashTree>\n                    <HTTPSamplerProxy guiclass=\"HttpTestSampleGui\" testclass=\"HTTPSamplerProxy\" testname=\"Forum View Discussion\" enabled=\"true\">\n                      <elementProp name=\"HTTPsampler.Arguments\" elementType=\"Arguments\" guiclass=\"HTTPArgumentsPanel\" testclass=\"Arguments\" testname=\"Argument List\" enabled=\"true\">\n                        <collectionProp name=\"Arguments.arguments\">\n                          <elementProp name=\"d\" elementType=\"HTTPArgument\">\n                            <boolProp name=\"HTTPArgument.always_encode\">false</boolProp>\n                            <stringProp name=\"Argument.value\">${discuss_id}</stringProp>\n                            <stringProp name=\"Argument.name\">d</stringProp>\n                            <stringProp name=\"Argument.metadata\">=</stringProp>\n                            <stringProp name=\"Argument.use_equals\">true</stringProp>\n                            <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                          </elementProp>\n                        </collectionProp>\n                      </elementProp>\n                      <stringProp name=\"HTTPSampler.domain\">${host}</stringProp>\n                      <stringProp name=\"HTTPSampler.port\">${port}</stringProp>\n                      <stringProp name=\"HTTPSampler.protocol\">${protocol}</stringProp>\n                      <stringProp name=\"HTTPSampler.contentEncoding\"></stringProp>\n                      <stringProp name=\"HTTPSampler.path\">/mod/forum/discuss.php</stringProp>\n                      <stringProp name=\"HTTPSampler.method\">GET</stringProp>\n                      <boolProp name=\"HTTPSampler.follow_redirects\">true</boolProp>\n                      <boolProp name=\"HTTPSampler.auto_redirects\">false</boolProp>\n                      <boolProp name=\"HTTPSampler.use_keepalive\">true</boolProp>\n                      <boolProp name=\"HTTPSampler.DO_MULTIPART_POST\">false</boolProp>\n                      <stringProp name=\"HTTPSampler.embedded_url_re\"></stringProp>\n                      <stringProp name=\"HTTPSampler.implementation\">Java</stringProp>\n                      <stringProp name=\"HTTPSampler.connect_timeout\"></stringProp>\n                      <stringProp name=\"HTTPSampler.response_timeout\"></stringProp>\n                      <stringProp name=\"TestPlan.comments\">View a forum discussion thread</stringProp>\n                    </HTTPSamplerProxy>\n                    <hashTree>\n                      <ResponseAssertion guiclass=\"AssertionGui\" testclass=\"ResponseAssertion\" testname=\"Assertion - in the discussion thread ?\" enabled=\"true\">\n                        <collectionProp name=\"Asserion.test_strings\">\n                          <stringProp name=\"1415837435\">${discuss_title}</stringProp>\n                          <stringProp name=\"-1185574437\">My Course list is</stringProp>\n                        </collectionProp>\n                        <stringProp name=\"Assertion.test_field\">Assertion.response_data</stringProp>\n                        <boolProp name=\"Assertion.assume_success\">false</boolProp>\n                        <intProp name=\"Assertion.test_type\">2</intProp>\n                        <stringProp name=\"Assertion.custom_message\"></stringProp>\n                      </ResponseAssertion>\n                      <hashTree/>\n                      <RegexExtractor guiclass=\"RegexExtractorGui\" testclass=\"RegexExtractor\" testname=\"RegExp Extractor - Get Forum id\" enabled=\"false\">\n                        <stringProp name=\"RegexExtractor.useHeaders\">false</stringProp>\n                        <stringProp name=\"RegexExtractor.refname\">forumid</stringProp>\n                        <stringProp name=\"RegexExtractor.regex\">name=&quot;forum&quot; value=&quot;(\\d+)&quot;</stringProp>\n                        <stringProp name=\"RegexExtractor.template\">$1$</stringProp>\n                        <stringProp name=\"RegexExtractor.default\">NULL</stringProp>\n                        <stringProp name=\"RegexExtractor.match_number\"></stringProp>\n                      </RegexExtractor>\n                      <hashTree/>\n                      <XPathExtractor guiclass=\"XPathExtractorGui\" testclass=\"XPathExtractor\" testname=\"XPath Extractor - Get forum id\" enabled=\"false\">\n                        <stringProp name=\"XPathExtractor.default\"></stringProp>\n                        <stringProp name=\"XPathExtractor.refname\">forumid</stringProp>\n                        <stringProp name=\"XPathExtractor.xpathQuery\">//*[@name=&apos;forum&apos;][1]/@value</stringProp>\n                        <boolProp name=\"XPathExtractor.validate\">false</boolProp>\n                        <boolProp name=\"XPathExtractor.tolerant\">true</boolProp>\n                        <boolProp name=\"XPathExtractor.namespace\">false</boolProp>\n                        <boolProp name=\"XPathExtractor.show_warnings\">true</boolProp>\n                        <boolProp name=\"XPathExtractor.report_errors\">true</boolProp>\n                        <boolProp name=\"XPathExtractor.quiet\">false</boolProp>\n                        <stringProp name=\"XPathExtractor.matchNumber\">-1</stringProp>\n                      </XPathExtractor>\n                      <hashTree/>\n                      <ConstantTimer guiclass=\"ConstantTimerGui\" testclass=\"ConstantTimer\" testname=\"Constant Timer\" enabled=\"false\">\n                        <stringProp name=\"ConstantTimer.delay\">1000</stringProp>\n                      </ConstantTimer>\n                      <hashTree/>\n                      <GaussianRandomTimer guiclass=\"GaussianRandomTimerGui\" testclass=\"GaussianRandomTimer\" testname=\"Gaussian Random Timer\" enabled=\"true\">\n                        <stringProp name=\"ConstantTimer.delay\">10000</stringProp>\n                        <stringProp name=\"RandomTimer.range\">4000</stringProp>\n                      </GaussianRandomTimer>\n                      <hashTree/>\n                    </hashTree>\n                  </hashTree>\n                </hashTree>\n                <IfController guiclass=\"IfControllerPanel\" testclass=\"IfController\" testname=\"Post a discussion (20% of the time)\" enabled=\"true\">\n                  <stringProp name=\"TestPlan.comments\">Only create a forum post (10% of the time a forum is viewed)</stringProp>\n                  <stringProp name=\"IfController.condition\">${__Random(1,20,ReuseRandomNumber)} &lt;= 20</stringProp>\n                  <boolProp name=\"IfController.evaluateAll\">false</boolProp>\n                </IfController>\n                <hashTree>\n                  <HTTPSamplerProxy guiclass=\"HttpTestSampleGui\" testclass=\"HTTPSamplerProxy\" testname=\"Click Add a New Discussion Topic\" enabled=\"true\">\n                    <elementProp name=\"HTTPsampler.Arguments\" elementType=\"Arguments\" guiclass=\"HTTPArgumentsPanel\" testclass=\"Arguments\" testname=\"Argument List\" enabled=\"true\">\n                      <collectionProp name=\"Arguments.arguments\">\n                        <elementProp name=\"forum\" elementType=\"HTTPArgument\">\n                          <boolProp name=\"HTTPArgument.always_encode\">false</boolProp>\n                          <stringProp name=\"Argument.value\">${forumid}</stringProp>\n                          <stringProp name=\"Argument.name\">forum</stringProp>\n                          <stringProp name=\"Argument.metadata\">=</stringProp>\n                          <stringProp name=\"Argument.use_equals\">true</stringProp>\n                          <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                        </elementProp>\n                      </collectionProp>\n                    </elementProp>\n                    <stringProp name=\"HTTPSampler.domain\">${host}</stringProp>\n                    <stringProp name=\"HTTPSampler.port\">${port}</stringProp>\n                    <stringProp name=\"HTTPSampler.protocol\">${protocol}</stringProp>\n                    <stringProp name=\"HTTPSampler.contentEncoding\"></stringProp>\n                    <stringProp name=\"HTTPSampler.path\">/mod/forum/post.php</stringProp>\n                    <stringProp name=\"HTTPSampler.method\">GET</stringProp>\n                    <boolProp name=\"HTTPSampler.follow_redirects\">true</boolProp>\n                    <boolProp name=\"HTTPSampler.auto_redirects\">false</boolProp>\n                    <boolProp name=\"HTTPSampler.use_keepalive\">true</boolProp>\n                    <boolProp name=\"HTTPSampler.DO_MULTIPART_POST\">false</boolProp>\n                    <stringProp name=\"HTTPSampler.embedded_url_re\"></stringProp>\n                    <stringProp name=\"HTTPSampler.implementation\">Java</stringProp>\n                    <stringProp name=\"HTTPSampler.connect_timeout\"></stringProp>\n                    <stringProp name=\"HTTPSampler.response_timeout\"></stringProp>\n                  </HTTPSamplerProxy>\n                  <hashTree>\n                    <ResponseAssertion guiclass=\"AssertionGui\" testclass=\"ResponseAssertion\" testname=\"Assertion - in the forum ?\" enabled=\"true\">\n                      <collectionProp name=\"Asserion.test_strings\">\n                        <stringProp name=\"1604416356\">Your new discussion topic</stringProp>\n                      </collectionProp>\n                      <stringProp name=\"Assertion.test_field\">Assertion.response_data</stringProp>\n                      <boolProp name=\"Assertion.assume_success\">false</boolProp>\n                      <intProp name=\"Assertion.test_type\">2</intProp>\n                      <stringProp name=\"Assertion.custom_message\"></stringProp>\n                    </ResponseAssertion>\n                    <hashTree/>\n                    <XPathExtractor guiclass=\"XPathExtractorGui\" testclass=\"XPathExtractor\" testname=\"XPath - Get discussion id\" enabled=\"false\">\n                      <stringProp name=\"XPathExtractor.default\"></stringProp>\n                      <stringProp name=\"XPathExtractor.refname\">discussionid</stringProp>\n                      <stringProp name=\"XPathExtractor.xpathQuery\">//*[@name=&apos;discussion&apos;][1]/@value</stringProp>\n                      <boolProp name=\"XPathExtractor.validate\">false</boolProp>\n                      <boolProp name=\"XPathExtractor.tolerant\">true</boolProp>\n                      <boolProp name=\"XPathExtractor.namespace\">false</boolProp>\n                      <boolProp name=\"XPathExtractor.show_warnings\">true</boolProp>\n                      <boolProp name=\"XPathExtractor.report_errors\">true</boolProp>\n                      <boolProp name=\"XPathExtractor.quiet\">false</boolProp>\n                      <stringProp name=\"XPathExtractor.matchNumber\">-1</stringProp>\n                    </XPathExtractor>\n                    <hashTree/>\n                    <RegexExtractor guiclass=\"RegexExtractorGui\" testclass=\"RegexExtractor\" testname=\"RegExp - Get discussion id\" enabled=\"true\">\n                      <stringProp name=\"RegexExtractor.useHeaders\">false</stringProp>\n                      <stringProp name=\"RegexExtractor.refname\">discussionid</stringProp>\n                      <stringProp name=\"RegexExtractor.regex\">name=&quot;discussion&quot;.*?value=&quot;([\\d]+)&quot;</stringProp>\n                      <stringProp name=\"RegexExtractor.template\">$1$</stringProp>\n                      <stringProp name=\"RegexExtractor.default\">NULL</stringProp>\n                      <stringProp name=\"RegexExtractor.match_number\">1</stringProp>\n                    </RegexExtractor>\n                    <hashTree/>\n                    <XPathExtractor guiclass=\"XPathExtractorGui\" testclass=\"XPathExtractor\" testname=\"XPath - Get parent id\" enabled=\"false\">\n                      <stringProp name=\"XPathExtractor.default\"></stringProp>\n                      <stringProp name=\"XPathExtractor.refname\">parentid</stringProp>\n                      <stringProp name=\"XPathExtractor.xpathQuery\">//*[@name=&apos;parent&apos;][1]/@value</stringProp>\n                      <boolProp name=\"XPathExtractor.validate\">false</boolProp>\n                      <boolProp name=\"XPathExtractor.tolerant\">true</boolProp>\n                      <boolProp name=\"XPathExtractor.namespace\">false</boolProp>\n                      <boolProp name=\"XPathExtractor.show_warnings\">true</boolProp>\n                      <boolProp name=\"XPathExtractor.report_errors\">true</boolProp>\n                      <boolProp name=\"XPathExtractor.quiet\">false</boolProp>\n                      <stringProp name=\"XPathExtractor.matchNumber\">-1</stringProp>\n                    </XPathExtractor>\n                    <hashTree/>\n                    <RegexExtractor guiclass=\"RegexExtractorGui\" testclass=\"RegexExtractor\" testname=\"RegExp - Get parent id\" enabled=\"true\">\n                      <stringProp name=\"RegexExtractor.useHeaders\">false</stringProp>\n                      <stringProp name=\"RegexExtractor.refname\">parentid</stringProp>\n                      <stringProp name=\"RegexExtractor.regex\">name=&quot;parent&quot;.*?value=&quot;([\\d]+)&quot;</stringProp>\n                      <stringProp name=\"RegexExtractor.template\">$1$</stringProp>\n                      <stringProp name=\"RegexExtractor.default\">NULL</stringProp>\n                      <stringProp name=\"RegexExtractor.match_number\">1</stringProp>\n                    </RegexExtractor>\n                    <hashTree/>\n                    <XPathExtractor guiclass=\"XPathExtractorGui\" testclass=\"XPathExtractor\" testname=\"XPath - Get user id\" enabled=\"false\">\n                      <stringProp name=\"XPathExtractor.default\"></stringProp>\n                      <stringProp name=\"XPathExtractor.refname\">userid</stringProp>\n                      <stringProp name=\"XPathExtractor.xpathQuery\">//*[@name=&apos;userid&apos;][1]/@value</stringProp>\n                      <boolProp name=\"XPathExtractor.validate\">false</boolProp>\n                      <boolProp name=\"XPathExtractor.tolerant\">true</boolProp>\n                      <boolProp name=\"XPathExtractor.namespace\">false</boolProp>\n                      <boolProp name=\"XPathExtractor.show_warnings\">true</boolProp>\n                      <boolProp name=\"XPathExtractor.report_errors\">true</boolProp>\n                      <boolProp name=\"XPathExtractor.quiet\">false</boolProp>\n                      <stringProp name=\"XPathExtractor.matchNumber\">-1</stringProp>\n                    </XPathExtractor>\n                    <hashTree/>\n                    <RegexExtractor guiclass=\"RegexExtractorGui\" testclass=\"RegexExtractor\" testname=\"RegExp - Get user id\" enabled=\"true\">\n                      <stringProp name=\"RegexExtractor.useHeaders\">false</stringProp>\n                      <stringProp name=\"RegexExtractor.refname\">userid</stringProp>\n                      <stringProp name=\"RegexExtractor.regex\">name=&quot;userid&quot;.*?value=&quot;([\\d]+)&quot;</stringProp>\n                      <stringProp name=\"RegexExtractor.template\">$1$</stringProp>\n                      <stringProp name=\"RegexExtractor.default\">NULL</stringProp>\n                      <stringProp name=\"RegexExtractor.match_number\">1</stringProp>\n                    </RegexExtractor>\n                    <hashTree/>\n                    <XPathExtractor guiclass=\"XPathExtractorGui\" testclass=\"XPathExtractor\" testname=\"XPath - Get group id\" enabled=\"false\">\n                      <stringProp name=\"XPathExtractor.default\"></stringProp>\n                      <stringProp name=\"XPathExtractor.refname\">groupid</stringProp>\n                      <stringProp name=\"XPathExtractor.xpathQuery\">//*[@name=&apos;groupid&apos;][1]/@value</stringProp>\n                      <boolProp name=\"XPathExtractor.validate\">false</boolProp>\n                      <boolProp name=\"XPathExtractor.tolerant\">true</boolProp>\n                      <boolProp name=\"XPathExtractor.namespace\">false</boolProp>\n                      <boolProp name=\"XPathExtractor.show_warnings\">true</boolProp>\n                      <boolProp name=\"XPathExtractor.report_errors\">true</boolProp>\n                      <boolProp name=\"XPathExtractor.quiet\">false</boolProp>\n                      <stringProp name=\"XPathExtractor.matchNumber\">-1</stringProp>\n                    </XPathExtractor>\n                    <hashTree/>\n                    <RegexExtractor guiclass=\"RegexExtractorGui\" testclass=\"RegexExtractor\" testname=\"RegExp - Get group id\" enabled=\"true\">\n                      <stringProp name=\"RegexExtractor.useHeaders\">false</stringProp>\n                      <stringProp name=\"RegexExtractor.refname\">groupid</stringProp>\n                      <stringProp name=\"RegexExtractor.regex\">name=&quot;groupid&quot;.*?value=&quot;([\\d]+)&quot;</stringProp>\n                      <stringProp name=\"RegexExtractor.template\">$1$</stringProp>\n                      <stringProp name=\"RegexExtractor.default\">NULL</stringProp>\n                      <stringProp name=\"RegexExtractor.match_number\">1</stringProp>\n                    </RegexExtractor>\n                    <hashTree/>\n                    <XPathExtractor guiclass=\"XPathExtractorGui\" testclass=\"XPathExtractor\" testname=\"XPath - Get edit id\" enabled=\"false\">\n                      <stringProp name=\"XPathExtractor.default\"></stringProp>\n                      <stringProp name=\"XPathExtractor.refname\">editid</stringProp>\n                      <stringProp name=\"XPathExtractor.xpathQuery\">//*[@name=&apos;edit&apos;][1]/@value</stringProp>\n                      <boolProp name=\"XPathExtractor.validate\">false</boolProp>\n                      <boolProp name=\"XPathExtractor.tolerant\">true</boolProp>\n                      <boolProp name=\"XPathExtractor.namespace\">false</boolProp>\n                      <boolProp name=\"XPathExtractor.show_warnings\">true</boolProp>\n                      <boolProp name=\"XPathExtractor.report_errors\">true</boolProp>\n                      <boolProp name=\"XPathExtractor.quiet\">false</boolProp>\n                      <stringProp name=\"XPathExtractor.matchNumber\">-1</stringProp>\n                    </XPathExtractor>\n                    <hashTree/>\n                    <RegexExtractor guiclass=\"RegexExtractorGui\" testclass=\"RegexExtractor\" testname=\"RegExp - Get edit id\" enabled=\"true\">\n                      <stringProp name=\"RegexExtractor.useHeaders\">false</stringProp>\n                      <stringProp name=\"RegexExtractor.refname\">editid</stringProp>\n                      <stringProp name=\"RegexExtractor.regex\">name=&quot;edit&quot;.*?value=&quot;([\\d]+)&quot;</stringProp>\n                      <stringProp name=\"RegexExtractor.template\">$1$</stringProp>\n                      <stringProp name=\"RegexExtractor.default\">NULL</stringProp>\n                      <stringProp name=\"RegexExtractor.match_number\">1</stringProp>\n                    </RegexExtractor>\n                    <hashTree/>\n                    <XPathExtractor guiclass=\"XPathExtractorGui\" testclass=\"XPathExtractor\" testname=\"XPath - Get attachment id\" enabled=\"false\">\n                      <stringProp name=\"XPathExtractor.default\"></stringProp>\n                      <stringProp name=\"XPathExtractor.refname\">attachmentid</stringProp>\n                      <stringProp name=\"XPathExtractor.xpathQuery\">//*[@name=&apos;attachment&apos;][1]/@value</stringProp>\n                      <boolProp name=\"XPathExtractor.validate\">false</boolProp>\n                      <boolProp name=\"XPathExtractor.tolerant\">true</boolProp>\n                      <boolProp name=\"XPathExtractor.namespace\">false</boolProp>\n                      <boolProp name=\"XPathExtractor.show_warnings\">true</boolProp>\n                      <boolProp name=\"XPathExtractor.report_errors\">true</boolProp>\n                      <boolProp name=\"XPathExtractor.quiet\">false</boolProp>\n                      <stringProp name=\"XPathExtractor.matchNumber\">-1</stringProp>\n                    </XPathExtractor>\n                    <hashTree/>\n                    <RegexExtractor guiclass=\"RegexExtractorGui\" testclass=\"RegexExtractor\" testname=\"RegExp - Get attachment id\" enabled=\"true\">\n                      <stringProp name=\"RegexExtractor.useHeaders\">false</stringProp>\n                      <stringProp name=\"RegexExtractor.refname\">attachmentid</stringProp>\n                      <stringProp name=\"RegexExtractor.regex\">name=&quot;attachment&quot;.*?value=&quot;([\\d]+)&quot;</stringProp>\n                      <stringProp name=\"RegexExtractor.template\">$1$</stringProp>\n                      <stringProp name=\"RegexExtractor.default\">NULL</stringProp>\n                      <stringProp name=\"RegexExtractor.match_number\">1</stringProp>\n                    </RegexExtractor>\n                    <hashTree/>\n                    <ConstantTimer guiclass=\"ConstantTimerGui\" testclass=\"ConstantTimer\" testname=\"Constant Timer\" enabled=\"false\">\n                      <stringProp name=\"ConstantTimer.delay\">1000</stringProp>\n                    </ConstantTimer>\n                    <hashTree/>\n                    <GaussianRandomTimer guiclass=\"GaussianRandomTimerGui\" testclass=\"GaussianRandomTimer\" testname=\"Gaussian Random Timer\" enabled=\"true\">\n                      <stringProp name=\"ConstantTimer.delay\">4000</stringProp>\n                      <stringProp name=\"RandomTimer.range\">3000</stringProp>\n                    </GaussianRandomTimer>\n                    <hashTree/>\n                    <XPathExtractor guiclass=\"XPathExtractorGui\" testclass=\"XPathExtractor\" testname=\"XPath - Get message itemid\" enabled=\"false\">\n                      <stringProp name=\"XPathExtractor.default\"></stringProp>\n                      <stringProp name=\"XPathExtractor.refname\">messageitemid</stringProp>\n                      <stringProp name=\"XPathExtractor.xpathQuery\">//*[@name=&apos;message[itemid]&apos;][1]/@value</stringProp>\n                      <boolProp name=\"XPathExtractor.validate\">false</boolProp>\n                      <boolProp name=\"XPathExtractor.tolerant\">true</boolProp>\n                      <boolProp name=\"XPathExtractor.namespace\">false</boolProp>\n                      <boolProp name=\"XPathExtractor.show_warnings\">true</boolProp>\n                      <boolProp name=\"XPathExtractor.report_errors\">true</boolProp>\n                      <boolProp name=\"XPathExtractor.quiet\">false</boolProp>\n                      <stringProp name=\"XPathExtractor.matchNumber\">-1</stringProp>\n                    </XPathExtractor>\n                    <hashTree/>\n                    <RegexExtractor guiclass=\"RegexExtractorGui\" testclass=\"RegexExtractor\" testname=\"RegExp - Get message itemid\" enabled=\"true\">\n                      <stringProp name=\"RegexExtractor.useHeaders\">false</stringProp>\n                      <stringProp name=\"RegexExtractor.refname\">messageitemid</stringProp>\n                      <stringProp name=\"RegexExtractor.regex\">name=&quot;message\\[itemid\\]&quot;.*?value=&quot;([\\d]+)&quot;</stringProp>\n                      <stringProp name=\"RegexExtractor.template\">$1$</stringProp>\n                      <stringProp name=\"RegexExtractor.default\">NULL</stringProp>\n                      <stringProp name=\"RegexExtractor.match_number\">1</stringProp>\n                    </RegexExtractor>\n                    <hashTree/>\n                  </hashTree>\n                  <DebugSampler guiclass=\"TestBeanGUI\" testclass=\"DebugSampler\" testname=\"Debug Sampler\" enabled=\"false\">\n                    <boolProp name=\"displayJMeterProperties\">false</boolProp>\n                    <boolProp name=\"displayJMeterVariables\">true</boolProp>\n                    <boolProp name=\"displaySystemProperties\">false</boolProp>\n                  </DebugSampler>\n                  <hashTree/>\n                  <ResultCollector guiclass=\"ViewResultsFullVisualizer\" testclass=\"ResultCollector\" testname=\"View Results Tree\" enabled=\"false\">\n                    <boolProp name=\"ResultCollector.error_logging\">false</boolProp>\n                    <objProp>\n                      <name>saveConfig</name>\n                      <value class=\"SampleSaveConfiguration\">\n                        <time>true</time>\n                        <latency>true</latency>\n                        <timestamp>true</timestamp>\n                        <success>true</success>\n                        <label>true</label>\n                        <code>true</code>\n                        <message>true</message>\n                        <threadName>true</threadName>\n                        <dataType>true</dataType>\n                        <encoding>false</encoding>\n                        <assertions>true</assertions>\n                        <subresults>true</subresults>\n                        <responseData>false</responseData>\n                        <samplerData>false</samplerData>\n                        <xml>false</xml>\n                        <fieldNames>false</fieldNames>\n                        <responseHeaders>false</responseHeaders>\n                        <requestHeaders>false</requestHeaders>\n                        <responseDataOnError>false</responseDataOnError>\n                        <saveAssertionResultsFailureMessage>false</saveAssertionResultsFailureMessage>\n                        <assertionsResultsToSave>0</assertionsResultsToSave>\n                        <bytes>true</bytes>\n                        <threadCounts>true</threadCounts>\n                      </value>\n                    </objProp>\n                    <stringProp name=\"filename\"></stringProp>\n                  </ResultCollector>\n                  <hashTree/>\n                  <HTTPSamplerProxy guiclass=\"HttpTestSampleGui\" testclass=\"HTTPSamplerProxy\" testname=\"Create a new topic\" enabled=\"true\">\n                    <elementProp name=\"HTTPsampler.Arguments\" elementType=\"Arguments\" guiclass=\"HTTPArgumentsPanel\" testclass=\"Arguments\" testname=\"Argument List\" enabled=\"true\">\n                      <collectionProp name=\"Arguments.arguments\">\n                        <elementProp name=\"_qf__mod_forum_post_form\" elementType=\"HTTPArgument\">\n                          <stringProp name=\"HTTPArgument.always_encode\">true</stringProp>\n                          <stringProp name=\"Argument.value\">1</stringProp>\n                          <stringProp name=\"Argument.name\">_qf__mod_forum_post_form</stringProp>\n                          <stringProp name=\"Argument.metadata\">=</stringProp>\n                          <stringProp name=\"Argument.use_equals\">true</stringProp>\n                          <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                        </elementProp>\n                        <elementProp name=\"attachments\" elementType=\"HTTPArgument\">\n                          <boolProp name=\"HTTPArgument.always_encode\">false</boolProp>\n                          <stringProp name=\"Argument.value\">${attachmentid}</stringProp>\n                          <stringProp name=\"Argument.metadata\">=</stringProp>\n                          <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                          <stringProp name=\"Argument.name\">attachments</stringProp>\n                        </elementProp>\n                        <elementProp name=\"course\" elementType=\"HTTPArgument\">\n                          <stringProp name=\"HTTPArgument.always_encode\">true</stringProp>\n                          <stringProp name=\"Argument.value\">${activity_course_id}</stringProp>\n                          <stringProp name=\"Argument.name\">course</stringProp>\n                          <stringProp name=\"Argument.metadata\">=</stringProp>\n                          <stringProp name=\"Argument.use_equals\">true</stringProp>\n                          <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                        </elementProp>\n                        <elementProp name=\"discussion\" elementType=\"HTTPArgument\">\n                          <stringProp name=\"HTTPArgument.always_encode\">true</stringProp>\n                          <stringProp name=\"Argument.value\">${discussionid}</stringProp>\n                          <stringProp name=\"Argument.name\">discussion</stringProp>\n                          <stringProp name=\"Argument.metadata\">=</stringProp>\n                          <stringProp name=\"Argument.use_equals\">true</stringProp>\n                          <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                        </elementProp>\n                        <elementProp name=\"edit\" elementType=\"HTTPArgument\">\n                          <stringProp name=\"HTTPArgument.always_encode\">true</stringProp>\n                          <stringProp name=\"Argument.value\">${editid}</stringProp>\n                          <stringProp name=\"Argument.name\">edit</stringProp>\n                          <stringProp name=\"Argument.metadata\">=</stringProp>\n                          <stringProp name=\"Argument.use_equals\">true</stringProp>\n                          <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                        </elementProp>\n                        <elementProp name=\"forum\" elementType=\"HTTPArgument\">\n                          <stringProp name=\"HTTPArgument.always_encode\">true</stringProp>\n                          <stringProp name=\"Argument.value\">${forumid}</stringProp>\n                          <stringProp name=\"Argument.name\">forum</stringProp>\n                          <stringProp name=\"Argument.metadata\">=</stringProp>\n                          <stringProp name=\"Argument.use_equals\">true</stringProp>\n                          <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                        </elementProp>\n                        <elementProp name=\"groupid\" elementType=\"HTTPArgument\">\n                          <stringProp name=\"HTTPArgument.always_encode\">true</stringProp>\n                          <stringProp name=\"Argument.value\">${groupid}</stringProp>\n                          <stringProp name=\"Argument.name\">groupid</stringProp>\n                          <stringProp name=\"Argument.metadata\">=</stringProp>\n                          <stringProp name=\"Argument.use_equals\">true</stringProp>\n                          <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                        </elementProp>\n                        <elementProp name=\"message[format]\" elementType=\"HTTPArgument\">\n                          <boolProp name=\"HTTPArgument.always_encode\">false</boolProp>\n                          <stringProp name=\"Argument.value\">1</stringProp>\n                          <stringProp name=\"Argument.metadata\">=</stringProp>\n                          <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                          <stringProp name=\"Argument.name\">message[format]</stringProp>\n                        </elementProp>\n                        <elementProp name=\"message[itemid]\" elementType=\"HTTPArgument\">\n                          <boolProp name=\"HTTPArgument.always_encode\">false</boolProp>\n                          <stringProp name=\"Argument.value\">${messageitemid}</stringProp>\n                          <stringProp name=\"Argument.metadata\">=</stringProp>\n                          <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                          <stringProp name=\"Argument.name\">message[itemid]</stringProp>\n                        </elementProp>\n                        <elementProp name=\"message[text]\" elementType=\"HTTPArgument\">\n                          <stringProp name=\"HTTPArgument.always_encode\">true</stringProp>\n                          <stringProp name=\"Argument.value\">&lt;p&gt;My Course list is ${courseList}&lt;/p&gt;</stringProp>\n                          <stringProp name=\"Argument.name\">message[text]</stringProp>\n                          <stringProp name=\"Argument.metadata\">=</stringProp>\n                          <stringProp name=\"Argument.use_equals\">true</stringProp>\n                          <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                        </elementProp>\n                        <elementProp name=\"parent\" elementType=\"HTTPArgument\">\n                          <stringProp name=\"HTTPArgument.always_encode\">true</stringProp>\n                          <stringProp name=\"Argument.value\">${parentid}</stringProp>\n                          <stringProp name=\"Argument.name\">parent</stringProp>\n                          <stringProp name=\"Argument.metadata\">=</stringProp>\n                          <stringProp name=\"Argument.use_equals\">true</stringProp>\n                          <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                        </elementProp>\n                        <elementProp name=\"reply\" elementType=\"HTTPArgument\">\n                          <stringProp name=\"HTTPArgument.always_encode\">true</stringProp>\n                          <stringProp name=\"Argument.value\">0</stringProp>\n                          <stringProp name=\"Argument.name\">reply</stringProp>\n                          <stringProp name=\"Argument.metadata\">=</stringProp>\n                          <stringProp name=\"Argument.use_equals\">true</stringProp>\n                          <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                        </elementProp>\n                        <elementProp name=\"sesskey\" elementType=\"HTTPArgument\">\n                          <stringProp name=\"HTTPArgument.always_encode\">true</stringProp>\n                          <stringProp name=\"Argument.value\">${sesskey}</stringProp>\n                          <stringProp name=\"Argument.name\">sesskey</stringProp>\n                          <stringProp name=\"Argument.metadata\">=</stringProp>\n                          <stringProp name=\"Argument.use_equals\">true</stringProp>\n                          <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                        </elementProp>\n                        <elementProp name=\"subject\" elementType=\"HTTPArgument\">\n                          <stringProp name=\"HTTPArgument.always_encode\">true</stringProp>\n                          <stringProp name=\"Argument.value\">${username} - ${RandomNumber}</stringProp>\n                          <stringProp name=\"Argument.name\">subject</stringProp>\n                          <stringProp name=\"Argument.metadata\">=</stringProp>\n                          <stringProp name=\"Argument.use_equals\">true</stringProp>\n                          <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                        </elementProp>\n                        <elementProp name=\"submitbutton\" elementType=\"HTTPArgument\">\n                          <boolProp name=\"HTTPArgument.always_encode\">false</boolProp>\n                          <stringProp name=\"Argument.value\">Post to forum</stringProp>\n                          <stringProp name=\"Argument.metadata\">=</stringProp>\n                          <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                          <stringProp name=\"Argument.name\">submitbutton</stringProp>\n                        </elementProp>\n                        <elementProp name=\"timestart\" elementType=\"HTTPArgument\">\n                          <stringProp name=\"HTTPArgument.always_encode\">true</stringProp>\n                          <stringProp name=\"Argument.value\">0</stringProp>\n                          <stringProp name=\"Argument.name\">timestart</stringProp>\n                          <stringProp name=\"Argument.metadata\">=</stringProp>\n                          <stringProp name=\"Argument.use_equals\">true</stringProp>\n                          <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                        </elementProp>\n                        <elementProp name=\"timeend\" elementType=\"HTTPArgument\">\n                          <stringProp name=\"HTTPArgument.always_encode\">true</stringProp>\n                          <stringProp name=\"Argument.value\">0</stringProp>\n                          <stringProp name=\"Argument.name\">timeend</stringProp>\n                          <stringProp name=\"Argument.metadata\">=</stringProp>\n                          <stringProp name=\"Argument.use_equals\">true</stringProp>\n                          <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                        </elementProp>\n                        <elementProp name=\"userid\" elementType=\"HTTPArgument\">\n                          <stringProp name=\"HTTPArgument.always_encode\">true</stringProp>\n                          <stringProp name=\"Argument.value\">${userid}</stringProp>\n                          <stringProp name=\"Argument.name\">userid</stringProp>\n                          <stringProp name=\"Argument.metadata\">=</stringProp>\n                          <stringProp name=\"Argument.use_equals\">true</stringProp>\n                          <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                        </elementProp>\n                      </collectionProp>\n                    </elementProp>\n                    <stringProp name=\"HTTPSampler.domain\">${host}</stringProp>\n                    <stringProp name=\"HTTPSampler.port\">${port}</stringProp>\n                    <stringProp name=\"HTTPSampler.protocol\">${protocol}</stringProp>\n                    <stringProp name=\"HTTPSampler.contentEncoding\"></stringProp>\n                    <stringProp name=\"HTTPSampler.path\">/mod/forum/post.php</stringProp>\n                    <stringProp name=\"HTTPSampler.method\">POST</stringProp>\n                    <boolProp name=\"HTTPSampler.follow_redirects\">true</boolProp>\n                    <boolProp name=\"HTTPSampler.auto_redirects\">false</boolProp>\n                    <boolProp name=\"HTTPSampler.use_keepalive\">true</boolProp>\n                    <boolProp name=\"HTTPSampler.DO_MULTIPART_POST\">true</boolProp>\n                    <stringProp name=\"HTTPSampler.embedded_url_re\"></stringProp>\n                    <stringProp name=\"HTTPSampler.implementation\">Java</stringProp>\n                    <stringProp name=\"HTTPSampler.connect_timeout\"></stringProp>\n                    <stringProp name=\"HTTPSampler.response_timeout\"></stringProp>\n                  </HTTPSamplerProxy>\n                  <hashTree>\n                    <ResponseAssertion guiclass=\"AssertionGui\" testclass=\"ResponseAssertion\" testname=\"Assertion - Response Contains Your post was successfully\" enabled=\"true\">\n                      <collectionProp name=\"Asserion.test_strings\">\n                        <stringProp name=\"-2123111183\">Your post was successfully</stringProp>\n                      </collectionProp>\n                      <intProp name=\"Assertion.test_type\">2</intProp>\n                      <stringProp name=\"Assertion.test_field\">Assertion.response_data</stringProp>\n                      <boolProp name=\"Assertion.assume_success\">false</boolProp>\n                      <stringProp name=\"Assertion.custom_message\"></stringProp>\n                    </ResponseAssertion>\n                    <hashTree/>\n                    <ConstantTimer guiclass=\"ConstantTimerGui\" testclass=\"ConstantTimer\" testname=\"Constant Timer\" enabled=\"true\">\n                      <stringProp name=\"ConstantTimer.delay\">1000</stringProp>\n                    </ConstantTimer>\n                    <hashTree/>\n                  </hashTree>\n                  <HTTPSamplerProxy guiclass=\"HttpTestSampleGui\" testclass=\"HTTPSamplerProxy\" testname=\"Refresh view of Forum Module; check is updated\" enabled=\"true\">\n                    <elementProp name=\"HTTPsampler.Arguments\" elementType=\"Arguments\" guiclass=\"HTTPArgumentsPanel\" testclass=\"Arguments\" testname=\"Argument List\" enabled=\"true\">\n                      <collectionProp name=\"Arguments.arguments\">\n                        <elementProp name=\"id\" elementType=\"HTTPArgument\">\n                          <boolProp name=\"HTTPArgument.always_encode\">false</boolProp>\n                          <stringProp name=\"Argument.value\">${moduleid}</stringProp>\n                          <stringProp name=\"Argument.name\">id</stringProp>\n                          <stringProp name=\"Argument.metadata\">=</stringProp>\n                          <stringProp name=\"Argument.use_equals\">true</stringProp>\n                          <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                        </elementProp>\n                      </collectionProp>\n                    </elementProp>\n                    <stringProp name=\"HTTPSampler.domain\">${host}</stringProp>\n                    <stringProp name=\"HTTPSampler.port\">${port}</stringProp>\n                    <stringProp name=\"HTTPSampler.protocol\">${protocol}</stringProp>\n                    <stringProp name=\"HTTPSampler.contentEncoding\"></stringProp>\n                    <stringProp name=\"HTTPSampler.path\">/mod/forum/view.php</stringProp>\n                    <stringProp name=\"HTTPSampler.method\">GET</stringProp>\n                    <boolProp name=\"HTTPSampler.follow_redirects\">true</boolProp>\n                    <boolProp name=\"HTTPSampler.auto_redirects\">false</boolProp>\n                    <boolProp name=\"HTTPSampler.use_keepalive\">true</boolProp>\n                    <boolProp name=\"HTTPSampler.DO_MULTIPART_POST\">false</boolProp>\n                    <stringProp name=\"HTTPSampler.embedded_url_re\"></stringProp>\n                    <stringProp name=\"HTTPSampler.implementation\">Java</stringProp>\n                    <stringProp name=\"HTTPSampler.connect_timeout\"></stringProp>\n                    <stringProp name=\"HTTPSampler.response_timeout\"></stringProp>\n                  </HTTPSamplerProxy>\n                  <hashTree>\n                    <ResponseAssertion guiclass=\"AssertionGui\" testclass=\"ResponseAssertion\" testname=\"Assertion - in the forum module ?\" enabled=\"true\">\n                      <collectionProp name=\"Asserion.test_strings\">\n                        <stringProp name=\"1915803329\">Performance Testing Forum</stringProp>\n                      </collectionProp>\n                      <stringProp name=\"Assertion.test_field\">Assertion.response_data</stringProp>\n                      <boolProp name=\"Assertion.assume_success\">false</boolProp>\n                      <intProp name=\"Assertion.test_type\">2</intProp>\n                      <stringProp name=\"Assertion.custom_message\"></stringProp>\n                    </ResponseAssertion>\n                    <hashTree/>\n                    <XPathExtractor guiclass=\"XPathExtractorGui\" testclass=\"XPathExtractor\" testname=\"XPath - Get the newly-created discussion id\" enabled=\"false\">\n                      <stringProp name=\"XPathExtractor.default\"></stringProp>\n                      <stringProp name=\"XPathExtractor.refname\">discussionid</stringProp>\n                      <stringProp name=\"XPathExtractor.xpathQuery\">substring-after(//*/a[contains(text(),&apos;${username} - ${RandomNumber}&apos;)][1]/@href,&apos;=&apos;)</stringProp>\n                      <boolProp name=\"XPathExtractor.validate\">false</boolProp>\n                      <boolProp name=\"XPathExtractor.tolerant\">true</boolProp>\n                      <boolProp name=\"XPathExtractor.namespace\">false</boolProp>\n                      <boolProp name=\"XPathExtractor.show_warnings\">true</boolProp>\n                      <boolProp name=\"XPathExtractor.report_errors\">true</boolProp>\n                      <boolProp name=\"XPathExtractor.quiet\">false</boolProp>\n                      <stringProp name=\"XPathExtractor.matchNumber\">-1</stringProp>\n                    </XPathExtractor>\n                    <hashTree/>\n                    <RegexExtractor guiclass=\"RegexExtractorGui\" testclass=\"RegexExtractor\" testname=\"RegExp - Get the newly-created discussion id\" enabled=\"false\">\n                      <stringProp name=\"RegexExtractor.useHeaders\">false</stringProp>\n                      <stringProp name=\"RegexExtractor.refname\">discussionid</stringProp>\n                      <stringProp name=\"RegexExtractor.regex\">name=&quot;message\\[itemid\\]&quot;.*?value=&quot;([\\d]+)&quot;</stringProp>\n                      <stringProp name=\"RegexExtractor.template\">$1$</stringProp>\n                      <stringProp name=\"RegexExtractor.default\">NULL</stringProp>\n                      <stringProp name=\"RegexExtractor.match_number\">1</stringProp>\n                    </RegexExtractor>\n                    <hashTree/>\n                    <ConstantTimer guiclass=\"ConstantTimerGui\" testclass=\"ConstantTimer\" testname=\"Constant Timer\" enabled=\"false\">\n                      <stringProp name=\"ConstantTimer.delay\">1000</stringProp>\n                    </ConstantTimer>\n                    <hashTree/>\n                    <GaussianRandomTimer guiclass=\"GaussianRandomTimerGui\" testclass=\"GaussianRandomTimer\" testname=\"Gaussian Random Timer\" enabled=\"true\">\n                      <stringProp name=\"ConstantTimer.delay\">4000</stringProp>\n                      <stringProp name=\"RandomTimer.range\">3000</stringProp>\n                    </GaussianRandomTimer>\n                    <hashTree/>\n                  </hashTree>\n                  <DebugSampler guiclass=\"TestBeanGUI\" testclass=\"DebugSampler\" testname=\"Debug Sampler\" enabled=\"false\">\n                    <boolProp name=\"displayJMeterProperties\">false</boolProp>\n                    <boolProp name=\"displayJMeterVariables\">true</boolProp>\n                    <boolProp name=\"displaySystemProperties\">false</boolProp>\n                  </DebugSampler>\n                  <hashTree/>\n                  <ResultCollector guiclass=\"ViewResultsFullVisualizer\" testclass=\"ResultCollector\" testname=\"View Results Tree\" enabled=\"false\">\n                    <boolProp name=\"ResultCollector.error_logging\">false</boolProp>\n                    <objProp>\n                      <name>saveConfig</name>\n                      <value class=\"SampleSaveConfiguration\">\n                        <time>true</time>\n                        <latency>true</latency>\n                        <timestamp>true</timestamp>\n                        <success>true</success>\n                        <label>true</label>\n                        <code>true</code>\n                        <message>true</message>\n                        <threadName>true</threadName>\n                        <dataType>true</dataType>\n                        <encoding>false</encoding>\n                        <assertions>true</assertions>\n                        <subresults>true</subresults>\n                        <responseData>false</responseData>\n                        <samplerData>false</samplerData>\n                        <xml>false</xml>\n                        <fieldNames>false</fieldNames>\n                        <responseHeaders>false</responseHeaders>\n                        <requestHeaders>false</requestHeaders>\n                        <responseDataOnError>false</responseDataOnError>\n                        <saveAssertionResultsFailureMessage>false</saveAssertionResultsFailureMessage>\n                        <assertionsResultsToSave>0</assertionsResultsToSave>\n                        <bytes>true</bytes>\n                        <threadCounts>true</threadCounts>\n                      </value>\n                    </objProp>\n                    <stringProp name=\"filename\"></stringProp>\n                  </ResultCollector>\n                  <hashTree/>\n                  <ResultCollector guiclass=\"GraphVisualizer\" testclass=\"ResultCollector\" testname=\"Graph Results\" enabled=\"false\">\n                    <boolProp name=\"ResultCollector.error_logging\">false</boolProp>\n                    <objProp>\n                      <name>saveConfig</name>\n                      <value class=\"SampleSaveConfiguration\">\n                        <time>true</time>\n                        <latency>true</latency>\n                        <timestamp>true</timestamp>\n                        <success>true</success>\n                        <label>true</label>\n                        <code>true</code>\n                        <message>true</message>\n                        <threadName>true</threadName>\n                        <dataType>true</dataType>\n                        <encoding>false</encoding>\n                        <assertions>true</assertions>\n                        <subresults>true</subresults>\n                        <responseData>false</responseData>\n                        <samplerData>false</samplerData>\n                        <xml>true</xml>\n                        <fieldNames>false</fieldNames>\n                        <responseHeaders>false</responseHeaders>\n                        <requestHeaders>false</requestHeaders>\n                        <responseDataOnError>false</responseDataOnError>\n                        <saveAssertionResultsFailureMessage>false</saveAssertionResultsFailureMessage>\n                        <assertionsResultsToSave>0</assertionsResultsToSave>\n                        <bytes>true</bytes>\n                        <threadCounts>true</threadCounts>\n                      </value>\n                    </objProp>\n                    <stringProp name=\"filename\"></stringProp>\n                  </ResultCollector>\n                  <hashTree/>\n                </hashTree>\n              </hashTree>\n              <IfController guiclass=\"IfControllerPanel\" testclass=\"IfController\" testname=\"Quiz (30% frequency the time)\" enabled=\"true\">\n                <stringProp name=\"IfController.condition\">${__Random(1,10,ReuseRandomNumber)} &lt;= 10</stringProp>\n                <boolProp name=\"IfController.evaluateAll\">false</boolProp>\n                <stringProp name=\"TestPlan.comments\">30% of the time, do the quiz</stringProp>\n              </IfController>\n              <hashTree>\n                <HTTPSamplerProxy guiclass=\"HttpTestSampleGui\" testclass=\"HTTPSamplerProxy\" testname=\"Goto course\" enabled=\"true\">\n                  <elementProp name=\"HTTPsampler.Arguments\" elementType=\"Arguments\" guiclass=\"HTTPArgumentsPanel\" testclass=\"Arguments\" testname=\"Argument List\" enabled=\"true\">\n                    <collectionProp name=\"Arguments.arguments\">\n                      <elementProp name=\"id\" elementType=\"HTTPArgument\">\n                        <boolProp name=\"HTTPArgument.always_encode\">false</boolProp>\n                        <stringProp name=\"Argument.value\">${activity_course_id}</stringProp>\n                        <stringProp name=\"Argument.name\">id</stringProp>\n                        <stringProp name=\"Argument.metadata\">=</stringProp>\n                        <stringProp name=\"Argument.use_equals\">true</stringProp>\n                        <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                      </elementProp>\n                    </collectionProp>\n                  </elementProp>\n                  <stringProp name=\"HTTPSampler.domain\">${host}</stringProp>\n                  <stringProp name=\"HTTPSampler.port\">${port}</stringProp>\n                  <stringProp name=\"HTTPSampler.protocol\">${protocol}</stringProp>\n                  <stringProp name=\"HTTPSampler.contentEncoding\"></stringProp>\n                  <stringProp name=\"HTTPSampler.path\">/course/view.php</stringProp>\n                  <stringProp name=\"HTTPSampler.method\">GET</stringProp>\n                  <boolProp name=\"HTTPSampler.follow_redirects\">true</boolProp>\n                  <boolProp name=\"HTTPSampler.auto_redirects\">false</boolProp>\n                  <boolProp name=\"HTTPSampler.use_keepalive\">true</boolProp>\n                  <boolProp name=\"HTTPSampler.DO_MULTIPART_POST\">false</boolProp>\n                  <stringProp name=\"HTTPSampler.embedded_url_re\"></stringProp>\n                  <stringProp name=\"HTTPSampler.implementation\">Java</stringProp>\n                  <stringProp name=\"HTTPSampler.connect_timeout\"></stringProp>\n                  <stringProp name=\"HTTPSampler.response_timeout\"></stringProp>\n                </HTTPSamplerProxy>\n                <hashTree>\n                  <ResponseAssertion guiclass=\"AssertionGui\" testclass=\"ResponseAssertion\" testname=\"Assertion - in the course ?\" enabled=\"true\">\n                    <collectionProp name=\"Asserion.test_strings\">\n                      <stringProp name=\"-1205900583\">Performance Testing Regular Quiz</stringProp>\n                    </collectionProp>\n                    <intProp name=\"Assertion.test_type\">2</intProp>\n                    <stringProp name=\"Assertion.test_field\">Assertion.response_data</stringProp>\n                    <boolProp name=\"Assertion.assume_success\">false</boolProp>\n                    <stringProp name=\"Assertion.custom_message\"></stringProp>\n                  </ResponseAssertion>\n                  <hashTree/>\n                  <RegexExtractor guiclass=\"RegexExtractorGui\" testclass=\"RegexExtractor\" testname=\"RegExp Extractor - Get forum discussion headings (if any!)\" enabled=\"false\">\n                    <stringProp name=\"RegexExtractor.useHeaders\">false</stringProp>\n                    <stringProp name=\"RegexExtractor.refname\">classtopics_html</stringProp>\n                    <stringProp name=\"RegexExtractor.regex\">(?s)(&lt;ul class=&quot;topics&quot;.*?)&lt;\\/div&gt;.*?&lt;\\/div&gt;.*?&lt;\\/div&gt;.*?&lt;\\/div&gt;.*?&lt;\\/div&gt;.*?&lt;div id=&quot;region-pre&quot;</stringProp>\n                    <stringProp name=\"RegexExtractor.template\">$1$</stringProp>\n                    <stringProp name=\"RegexExtractor.default\">NULL</stringProp>\n                    <stringProp name=\"RegexExtractor.match_number\"></stringProp>\n                  </RegexExtractor>\n                  <hashTree/>\n                  <RegexExtractor guiclass=\"RegexExtractorGui\" testclass=\"RegexExtractor\" testname=\"RegExp Extractor - Get perf quiz chunk\" enabled=\"true\">\n                    <stringProp name=\"RegexExtractor.useHeaders\">false</stringProp>\n                    <stringProp name=\"RegexExtractor.refname\">html_chunk_perfquiz</stringProp>\n                    <stringProp name=\"RegexExtractor.regex\">(.{0,250}Performance Testing Regular Quiz)</stringProp>\n                    <stringProp name=\"RegexExtractor.template\">$1$</stringProp>\n                    <stringProp name=\"RegexExtractor.default\">NULL</stringProp>\n                    <stringProp name=\"RegexExtractor.match_number\"></stringProp>\n                  </RegexExtractor>\n                  <hashTree/>\n                  <RegexExtractor guiclass=\"RegexExtractorGui\" testclass=\"RegexExtractor\" testname=\"RegExp Extractor - Get forum discussion headings (if any!)\" enabled=\"true\">\n                    <stringProp name=\"RegexExtractor.useHeaders\">false</stringProp>\n                    <stringProp name=\"RegexExtractor.refname\">moduleid</stringProp>\n                    <stringProp name=\"RegexExtractor.regex\">\\/mod\\/quiz\\/view\\.php\\?id=(\\d+)</stringProp>\n                    <stringProp name=\"RegexExtractor.template\">$1$</stringProp>\n                    <stringProp name=\"RegexExtractor.default\">NULL</stringProp>\n                    <stringProp name=\"RegexExtractor.match_number\"></stringProp>\n                    <stringProp name=\"Sample.scope\">variable</stringProp>\n                    <stringProp name=\"Scope.variable\">html_chunk_perfquiz</stringProp>\n                  </RegexExtractor>\n                  <hashTree/>\n                  <XPathExtractor guiclass=\"XPathExtractorGui\" testclass=\"XPathExtractor\" testname=\"XPath Extractor - Get Performance Test Regular Quiz id\" enabled=\"false\">\n                    <stringProp name=\"XPathExtractor.default\">NULL</stringProp>\n                    <stringProp name=\"XPathExtractor.refname\">moduleid</stringProp>\n                    <stringProp name=\"XPathExtractor.xpathQuery\">substring-after(//*[contains(text(),&apos;Performance Testing Regular Quiz&apos;)]/../@href,&apos;id=&apos;)</stringProp>\n                    <boolProp name=\"XPathExtractor.validate\">false</boolProp>\n                    <boolProp name=\"XPathExtractor.tolerant\">true</boolProp>\n                    <boolProp name=\"XPathExtractor.namespace\">false</boolProp>\n                    <boolProp name=\"XPathExtractor.show_warnings\">true</boolProp>\n                    <boolProp name=\"XPathExtractor.quiet\">false</boolProp>\n                    <boolProp name=\"XPathExtractor.report_errors\">true</boolProp>\n                    <stringProp name=\"Sample.scope\">variable</stringProp>\n                    <stringProp name=\"Scope.variable\">classtopics_html</stringProp>\n                    <stringProp name=\"XPathExtractor.matchNumber\">-1</stringProp>\n                  </XPathExtractor>\n                  <hashTree/>\n                  <XPathExtractor guiclass=\"XPathExtractorGui\" testclass=\"XPathExtractor\" testname=\"Get module id\" enabled=\"false\">\n                    <stringProp name=\"XPathExtractor.default\"></stringProp>\n                    <stringProp name=\"XPathExtractor.refname\">moduleid</stringProp>\n                    <stringProp name=\"XPathExtractor.xpathQuery\">substring-after(//*/a/*[contains(text(),&apos;Performance Testing Regular Quiz&apos;)]/../@href,&apos;=&apos;)</stringProp>\n                    <boolProp name=\"XPathExtractor.validate\">false</boolProp>\n                    <boolProp name=\"XPathExtractor.tolerant\">true</boolProp>\n                    <boolProp name=\"XPathExtractor.namespace\">false</boolProp>\n                    <boolProp name=\"XPathExtractor.show_warnings\">true</boolProp>\n                    <boolProp name=\"XPathExtractor.report_errors\">true</boolProp>\n                    <boolProp name=\"XPathExtractor.quiet\">false</boolProp>\n                    <stringProp name=\"XPathExtractor.matchNumber\">-1</stringProp>\n                  </XPathExtractor>\n                  <hashTree/>\n                  <GaussianRandomTimer guiclass=\"GaussianRandomTimerGui\" testclass=\"GaussianRandomTimer\" testname=\"Gaussian Random Timer\" enabled=\"true\">\n                    <stringProp name=\"ConstantTimer.delay\">4000</stringProp>\n                    <stringProp name=\"RandomTimer.range\">3000</stringProp>\n                  </GaussianRandomTimer>\n                  <hashTree/>\n                  <ConstantTimer guiclass=\"ConstantTimerGui\" testclass=\"ConstantTimer\" testname=\"Constant Timer\" enabled=\"false\">\n                    <stringProp name=\"ConstantTimer.delay\">1000</stringProp>\n                  </ConstantTimer>\n                  <hashTree/>\n                </hashTree>\n                <HTTPSamplerProxy guiclass=\"HttpTestSampleGui\" testclass=\"HTTPSamplerProxy\" testname=\"Open the quiz\" enabled=\"true\">\n                  <elementProp name=\"HTTPsampler.Arguments\" elementType=\"Arguments\" guiclass=\"HTTPArgumentsPanel\" testclass=\"Arguments\" testname=\"Argument List\" enabled=\"true\">\n                    <collectionProp name=\"Arguments.arguments\">\n                      <elementProp name=\"id\" elementType=\"HTTPArgument\">\n                        <boolProp name=\"HTTPArgument.always_encode\">false</boolProp>\n                        <stringProp name=\"Argument.value\">${moduleid}</stringProp>\n                        <stringProp name=\"Argument.name\">id</stringProp>\n                        <stringProp name=\"Argument.metadata\">=</stringProp>\n                        <stringProp name=\"Argument.use_equals\">true</stringProp>\n                        <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                      </elementProp>\n                    </collectionProp>\n                  </elementProp>\n                  <stringProp name=\"HTTPSampler.domain\">${host}</stringProp>\n                  <stringProp name=\"HTTPSampler.port\">${port}</stringProp>\n                  <stringProp name=\"HTTPSampler.protocol\">${protocol}</stringProp>\n                  <stringProp name=\"HTTPSampler.contentEncoding\"></stringProp>\n                  <stringProp name=\"HTTPSampler.path\">/mod/quiz/view.php</stringProp>\n                  <stringProp name=\"HTTPSampler.method\">GET</stringProp>\n                  <boolProp name=\"HTTPSampler.follow_redirects\">true</boolProp>\n                  <boolProp name=\"HTTPSampler.auto_redirects\">false</boolProp>\n                  <boolProp name=\"HTTPSampler.use_keepalive\">true</boolProp>\n                  <boolProp name=\"HTTPSampler.DO_MULTIPART_POST\">false</boolProp>\n                  <stringProp name=\"HTTPSampler.embedded_url_re\"></stringProp>\n                  <stringProp name=\"HTTPSampler.implementation\">Java</stringProp>\n                  <stringProp name=\"HTTPSampler.connect_timeout\"></stringProp>\n                  <stringProp name=\"HTTPSampler.response_timeout\"></stringProp>\n                </HTTPSamplerProxy>\n                <hashTree>\n                  <ResponseAssertion guiclass=\"AssertionGui\" testclass=\"ResponseAssertion\" testname=\"Assertion - in the quiz entry screen?\" enabled=\"true\">\n                    <collectionProp name=\"Asserion.test_strings\">\n                      <stringProp name=\"-1205900583\">Performance Testing Regular Quiz</stringProp>\n                    </collectionProp>\n                    <stringProp name=\"Assertion.test_field\">Assertion.response_data</stringProp>\n                    <boolProp name=\"Assertion.assume_success\">false</boolProp>\n                    <intProp name=\"Assertion.test_type\">2</intProp>\n                    <stringProp name=\"Assertion.custom_message\"></stringProp>\n                  </ResponseAssertion>\n                  <hashTree/>\n                  <XPathExtractor guiclass=\"XPathExtractorGui\" testclass=\"XPathExtractor\" testname=\"Get quiz id\" enabled=\"false\">\n                    <stringProp name=\"XPathExtractor.default\"></stringProp>\n                    <stringProp name=\"XPathExtractor.refname\">quizid</stringProp>\n                    <stringProp name=\"XPathExtractor.xpathQuery\">//*/form[@action=&quot;https://${host}/mod/quiz/startattempt.php&quot;]//*/input[@name=&apos;cmid&apos;][1]/@value</stringProp>\n                    <boolProp name=\"XPathExtractor.validate\">false</boolProp>\n                    <boolProp name=\"XPathExtractor.tolerant\">true</boolProp>\n                    <boolProp name=\"XPathExtractor.namespace\">false</boolProp>\n                    <boolProp name=\"XPathExtractor.show_warnings\">true</boolProp>\n                    <boolProp name=\"XPathExtractor.report_errors\">true</boolProp>\n                    <boolProp name=\"XPathExtractor.quiet\">false</boolProp>\n                    <stringProp name=\"XPathExtractor.matchNumber\">-1</stringProp>\n                  </XPathExtractor>\n                  <hashTree/>\n                  <GaussianRandomTimer guiclass=\"GaussianRandomTimerGui\" testclass=\"GaussianRandomTimer\" testname=\"Gaussian Random Timer\" enabled=\"true\">\n                    <stringProp name=\"ConstantTimer.delay\">10000</stringProp>\n                    <stringProp name=\"RandomTimer.range\">3000</stringProp>\n                  </GaussianRandomTimer>\n                  <hashTree/>\n                  <ConstantTimer guiclass=\"ConstantTimerGui\" testclass=\"ConstantTimer\" testname=\"Constant Timer\" enabled=\"false\">\n                    <stringProp name=\"ConstantTimer.delay\">1000</stringProp>\n                  </ConstantTimer>\n                  <hashTree/>\n                </hashTree>\n                <HTTPSamplerProxy guiclass=\"HttpTestSampleGui\" testclass=\"HTTPSamplerProxy\" testname=\"Click on Attempt the quiz now\" enabled=\"true\">\n                  <elementProp name=\"HTTPsampler.Arguments\" elementType=\"Arguments\" guiclass=\"HTTPArgumentsPanel\" testclass=\"Arguments\" testname=\"Argument List\" enabled=\"true\">\n                    <collectionProp name=\"Arguments.arguments\">\n                      <elementProp name=\"cmid\" elementType=\"HTTPArgument\">\n                        <boolProp name=\"HTTPArgument.always_encode\">false</boolProp>\n                        <stringProp name=\"Argument.value\">${moduleid}</stringProp>\n                        <stringProp name=\"Argument.name\">cmid</stringProp>\n                        <stringProp name=\"Argument.metadata\">=</stringProp>\n                        <stringProp name=\"Argument.use_equals\">true</stringProp>\n                        <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                      </elementProp>\n                      <elementProp name=\"sesskey\" elementType=\"HTTPArgument\">\n                        <boolProp name=\"HTTPArgument.always_encode\">false</boolProp>\n                        <stringProp name=\"Argument.value\">${sesskey}</stringProp>\n                        <stringProp name=\"Argument.metadata\">=</stringProp>\n                        <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                        <stringProp name=\"Argument.name\">sesskey</stringProp>\n                      </elementProp>\n                    </collectionProp>\n                  </elementProp>\n                  <stringProp name=\"HTTPSampler.domain\">${host}</stringProp>\n                  <stringProp name=\"HTTPSampler.port\">${port}</stringProp>\n                  <stringProp name=\"HTTPSampler.protocol\">${protocol}</stringProp>\n                  <stringProp name=\"HTTPSampler.contentEncoding\"></stringProp>\n                  <stringProp name=\"HTTPSampler.path\">/mod/quiz/startattempt.php</stringProp>\n                  <stringProp name=\"HTTPSampler.method\">POST</stringProp>\n                  <boolProp name=\"HTTPSampler.follow_redirects\">true</boolProp>\n                  <boolProp name=\"HTTPSampler.auto_redirects\">false</boolProp>\n                  <boolProp name=\"HTTPSampler.use_keepalive\">true</boolProp>\n                  <boolProp name=\"HTTPSampler.DO_MULTIPART_POST\">false</boolProp>\n                  <stringProp name=\"HTTPSampler.embedded_url_re\"></stringProp>\n                  <stringProp name=\"HTTPSampler.implementation\">Java</stringProp>\n                  <stringProp name=\"HTTPSampler.connect_timeout\"></stringProp>\n                  <stringProp name=\"HTTPSampler.response_timeout\"></stringProp>\n                </HTTPSamplerProxy>\n                <hashTree>\n                  <ResponseAssertion guiclass=\"AssertionGui\" testclass=\"ResponseAssertion\" testname=\"Assertion - in the quiz ?\" enabled=\"true\">\n                    <collectionProp name=\"Asserion.test_strings\">\n                      <stringProp name=\"-1545612013\">What&apos;s your username</stringProp>\n                      <stringProp name=\"-2127233155\">Is the LMS Performing acceptably?</stringProp>\n                      <stringProp name=\"-1512513172\">What is 2 \\+ 2\\?</stringProp>\n                    </collectionProp>\n                    <stringProp name=\"Assertion.test_field\">Assertion.response_data</stringProp>\n                    <boolProp name=\"Assertion.assume_success\">false</boolProp>\n                    <intProp name=\"Assertion.test_type\">2</intProp>\n                    <stringProp name=\"Assertion.custom_message\"></stringProp>\n                  </ResponseAssertion>\n                  <hashTree/>\n                  <GaussianRandomTimer guiclass=\"GaussianRandomTimerGui\" testclass=\"GaussianRandomTimer\" testname=\"Gaussian Random Timer\" enabled=\"true\">\n                    <stringProp name=\"ConstantTimer.delay\">10000</stringProp>\n                    <stringProp name=\"RandomTimer.range\">3000</stringProp>\n                  </GaussianRandomTimer>\n                  <hashTree/>\n                  <ConstantTimer guiclass=\"ConstantTimerGui\" testclass=\"ConstantTimer\" testname=\"Constant Timer\" enabled=\"false\">\n                    <stringProp name=\"ConstantTimer.delay\">1000</stringProp>\n                  </ConstantTimer>\n                  <hashTree/>\n                  <XPathExtractor guiclass=\"XPathExtractorGui\" testclass=\"XPathExtractor\" testname=\"Get attemptid\" enabled=\"false\">\n                    <stringProp name=\"XPathExtractor.default\"></stringProp>\n                    <stringProp name=\"XPathExtractor.refname\">attemptid</stringProp>\n                    <stringProp name=\"XPathExtractor.xpathQuery\">//*/form[@action=&quot;https://${host}/mod/quiz/processattempt.php&quot;]//*/input[@name=&apos;attempt&apos;][1]/@value</stringProp>\n                    <boolProp name=\"XPathExtractor.validate\">false</boolProp>\n                    <boolProp name=\"XPathExtractor.tolerant\">true</boolProp>\n                    <boolProp name=\"XPathExtractor.namespace\">false</boolProp>\n                    <boolProp name=\"XPathExtractor.show_warnings\">true</boolProp>\n                    <boolProp name=\"XPathExtractor.report_errors\">true</boolProp>\n                    <boolProp name=\"XPathExtractor.quiet\">false</boolProp>\n                    <stringProp name=\"XPathExtractor.matchNumber\">-1</stringProp>\n                  </XPathExtractor>\n                  <hashTree/>\n                  <RegexExtractor guiclass=\"RegexExtractorGui\" testclass=\"RegexExtractor\" testname=\"RegExp - Get attemptip\" enabled=\"true\">\n                    <stringProp name=\"RegexExtractor.useHeaders\">false</stringProp>\n                    <stringProp name=\"RegexExtractor.refname\">attemptid</stringProp>\n                    <stringProp name=\"RegexExtractor.regex\">\\/mod\\/quiz\\/attempt\\.php\\?attempt=(\\d+)&quot;&gt;Continue&lt;\\/a&gt;</stringProp>\n                    <stringProp name=\"RegexExtractor.template\">$1$</stringProp>\n                    <stringProp name=\"RegexExtractor.default\">NULL</stringProp>\n                    <stringProp name=\"RegexExtractor.match_number\"></stringProp>\n                    <stringProp name=\"Sample.scope\">children</stringProp>\n                  </RegexExtractor>\n                  <hashTree/>\n                  <ResultCollector guiclass=\"ViewResultsFullVisualizer\" testclass=\"ResultCollector\" testname=\"View Results Tree\" enabled=\"false\">\n                    <boolProp name=\"ResultCollector.error_logging\">false</boolProp>\n                    <objProp>\n                      <name>saveConfig</name>\n                      <value class=\"SampleSaveConfiguration\">\n                        <time>true</time>\n                        <latency>true</latency>\n                        <timestamp>true</timestamp>\n                        <success>true</success>\n                        <label>true</label>\n                        <code>true</code>\n                        <message>true</message>\n                        <threadName>true</threadName>\n                        <dataType>true</dataType>\n                        <encoding>false</encoding>\n                        <assertions>true</assertions>\n                        <subresults>true</subresults>\n                        <responseData>false</responseData>\n                        <samplerData>false</samplerData>\n                        <xml>false</xml>\n                        <fieldNames>true</fieldNames>\n                        <responseHeaders>false</responseHeaders>\n                        <requestHeaders>false</requestHeaders>\n                        <responseDataOnError>false</responseDataOnError>\n                        <saveAssertionResultsFailureMessage>true</saveAssertionResultsFailureMessage>\n                        <assertionsResultsToSave>0</assertionsResultsToSave>\n                        <bytes>true</bytes>\n                        <sentBytes>true</sentBytes>\n                        <threadCounts>true</threadCounts>\n                        <idleTime>true</idleTime>\n                        <connectTime>true</connectTime>\n                      </value>\n                    </objProp>\n                    <stringProp name=\"filename\"></stringProp>\n                  </ResultCollector>\n                  <hashTree/>\n                </hashTree>\n                <HTTPSamplerProxy guiclass=\"HttpTestSampleGui\" testclass=\"HTTPSamplerProxy\" testname=\"Submit the quiz attempt\" enabled=\"true\">\n                  <elementProp name=\"HTTPsampler.Arguments\" elementType=\"Arguments\" guiclass=\"HTTPArgumentsPanel\" testclass=\"Arguments\" testname=\"Argument List\" enabled=\"true\">\n                    <collectionProp name=\"Arguments.arguments\">\n                      <elementProp name=\"q${attemptid}:1_:flagged\" elementType=\"HTTPArgument\">\n                        <boolProp name=\"HTTPArgument.always_encode\">false</boolProp>\n                        <stringProp name=\"Argument.value\">0</stringProp>\n                        <stringProp name=\"Argument.metadata\">=</stringProp>\n                        <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                        <stringProp name=\"Argument.name\">q${attemptid}:1_:flagged</stringProp>\n                      </elementProp>\n                      <elementProp name=\"q${attemptid}:1_:sequencecheck\" elementType=\"HTTPArgument\">\n                        <boolProp name=\"HTTPArgument.always_encode\">false</boolProp>\n                        <stringProp name=\"Argument.value\">1</stringProp>\n                        <stringProp name=\"Argument.metadata\">=</stringProp>\n                        <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                        <stringProp name=\"Argument.name\">q${attemptid}:1_:sequencecheck</stringProp>\n                      </elementProp>\n                      <elementProp name=\"q${attemptid}:1_answer\" elementType=\"HTTPArgument\">\n                        <boolProp name=\"HTTPArgument.always_encode\">false</boolProp>\n                        <stringProp name=\"Argument.value\">${username}@${user_type}</stringProp>\n                        <stringProp name=\"Argument.metadata\">=</stringProp>\n                        <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                        <stringProp name=\"Argument.name\">q${attemptid}:1_answer</stringProp>\n                      </elementProp>\n                      <elementProp name=\"q${attemptid}:2_:flagged\" elementType=\"HTTPArgument\">\n                        <boolProp name=\"HTTPArgument.always_encode\">false</boolProp>\n                        <stringProp name=\"Argument.value\">0</stringProp>\n                        <stringProp name=\"Argument.metadata\">=</stringProp>\n                        <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                        <stringProp name=\"Argument.name\">q${attemptid}:2_:flagged</stringProp>\n                      </elementProp>\n                      <elementProp name=\"q${attemptid}:2_:sequencecheck\" elementType=\"HTTPArgument\">\n                        <boolProp name=\"HTTPArgument.always_encode\">false</boolProp>\n                        <stringProp name=\"Argument.value\">1</stringProp>\n                        <stringProp name=\"Argument.metadata\">=</stringProp>\n                        <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                        <stringProp name=\"Argument.name\">q${attemptid}:2_:sequencecheck</stringProp>\n                      </elementProp>\n                      <elementProp name=\"q${attemptid}:2_answer\" elementType=\"HTTPArgument\">\n                        <boolProp name=\"HTTPArgument.always_encode\">false</boolProp>\n                        <stringProp name=\"Argument.value\">${__Random(0,1)}</stringProp>\n                        <stringProp name=\"Argument.metadata\">=</stringProp>\n                        <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                        <stringProp name=\"Argument.name\">q${attemptid}:2_answer</stringProp>\n                      </elementProp>\n                      <elementProp name=\"q${attemptid}:3_:flagged\" elementType=\"HTTPArgument\">\n                        <boolProp name=\"HTTPArgument.always_encode\">false</boolProp>\n                        <stringProp name=\"Argument.value\">0</stringProp>\n                        <stringProp name=\"Argument.metadata\">=</stringProp>\n                        <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                        <stringProp name=\"Argument.name\">q${attemptid}:3_:flagged</stringProp>\n                      </elementProp>\n                      <elementProp name=\"q${attemptid}:3_:sequencecheck\" elementType=\"HTTPArgument\">\n                        <boolProp name=\"HTTPArgument.always_encode\">false</boolProp>\n                        <stringProp name=\"Argument.value\">1</stringProp>\n                        <stringProp name=\"Argument.metadata\">=</stringProp>\n                        <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                        <stringProp name=\"Argument.name\">q${attemptid}:3_:sequencecheck</stringProp>\n                      </elementProp>\n                      <elementProp name=\"q${attemptid}:3_answer\" elementType=\"HTTPArgument\">\n                        <boolProp name=\"HTTPArgument.always_encode\">false</boolProp>\n                        <stringProp name=\"Argument.value\">${__Random(3,5)}</stringProp>\n                        <stringProp name=\"Argument.metadata\">=</stringProp>\n                        <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                        <stringProp name=\"Argument.name\">q${attemptid}:3_answer</stringProp>\n                      </elementProp>\n                      <elementProp name=\"next\" elementType=\"HTTPArgument\">\n                        <boolProp name=\"HTTPArgument.always_encode\">false</boolProp>\n                        <stringProp name=\"Argument.value\">Next</stringProp>\n                        <stringProp name=\"Argument.metadata\">=</stringProp>\n                        <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                        <stringProp name=\"Argument.name\">next</stringProp>\n                      </elementProp>\n                      <elementProp name=\"attempt\" elementType=\"HTTPArgument\">\n                        <boolProp name=\"HTTPArgument.always_encode\">false</boolProp>\n                        <stringProp name=\"Argument.value\">${attemptid}</stringProp>\n                        <stringProp name=\"Argument.metadata\">=</stringProp>\n                        <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                        <stringProp name=\"Argument.name\">attempt</stringProp>\n                      </elementProp>\n                      <elementProp name=\"thispage\" elementType=\"HTTPArgument\">\n                        <boolProp name=\"HTTPArgument.always_encode\">false</boolProp>\n                        <stringProp name=\"Argument.value\">0</stringProp>\n                        <stringProp name=\"Argument.metadata\">=</stringProp>\n                        <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                        <stringProp name=\"Argument.name\">thispage</stringProp>\n                      </elementProp>\n                      <elementProp name=\"nextpage\" elementType=\"HTTPArgument\">\n                        <boolProp name=\"HTTPArgument.always_encode\">false</boolProp>\n                        <stringProp name=\"Argument.value\">-1</stringProp>\n                        <stringProp name=\"Argument.metadata\">=</stringProp>\n                        <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                        <stringProp name=\"Argument.name\">nextpage</stringProp>\n                      </elementProp>\n                      <elementProp name=\"timeup\" elementType=\"HTTPArgument\">\n                        <boolProp name=\"HTTPArgument.always_encode\">false</boolProp>\n                        <stringProp name=\"Argument.value\">0</stringProp>\n                        <stringProp name=\"Argument.metadata\">=</stringProp>\n                        <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                        <stringProp name=\"Argument.name\">timeup</stringProp>\n                      </elementProp>\n                      <elementProp name=\"sesskey\" elementType=\"HTTPArgument\">\n                        <boolProp name=\"HTTPArgument.always_encode\">false</boolProp>\n                        <stringProp name=\"Argument.value\">${sesskey}</stringProp>\n                        <stringProp name=\"Argument.metadata\">=</stringProp>\n                        <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                        <stringProp name=\"Argument.name\">sesskey</stringProp>\n                      </elementProp>\n                      <elementProp name=\"scrollpos\" elementType=\"HTTPArgument\">\n                        <boolProp name=\"HTTPArgument.always_encode\">false</boolProp>\n                        <stringProp name=\"Argument.value\"></stringProp>\n                        <stringProp name=\"Argument.metadata\">=</stringProp>\n                        <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                        <stringProp name=\"Argument.name\">scrollpos</stringProp>\n                      </elementProp>\n                      <elementProp name=\"slots\" elementType=\"HTTPArgument\">\n                        <boolProp name=\"HTTPArgument.always_encode\">false</boolProp>\n                        <stringProp name=\"Argument.value\">1,2,3</stringProp>\n                        <stringProp name=\"Argument.metadata\">=</stringProp>\n                        <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                        <stringProp name=\"Argument.name\">slots</stringProp>\n                      </elementProp>\n                    </collectionProp>\n                  </elementProp>\n                  <stringProp name=\"HTTPSampler.domain\">${host}</stringProp>\n                  <stringProp name=\"HTTPSampler.port\">${port}</stringProp>\n                  <stringProp name=\"HTTPSampler.protocol\">${protocol}</stringProp>\n                  <stringProp name=\"HTTPSampler.contentEncoding\">utf-8</stringProp>\n                  <stringProp name=\"HTTPSampler.path\">/mod/quiz/processattempt.php</stringProp>\n                  <stringProp name=\"HTTPSampler.method\">POST</stringProp>\n                  <boolProp name=\"HTTPSampler.follow_redirects\">true</boolProp>\n                  <boolProp name=\"HTTPSampler.auto_redirects\">false</boolProp>\n                  <boolProp name=\"HTTPSampler.use_keepalive\">true</boolProp>\n                  <boolProp name=\"HTTPSampler.DO_MULTIPART_POST\">true</boolProp>\n                  <stringProp name=\"HTTPSampler.embedded_url_re\"></stringProp>\n                  <stringProp name=\"HTTPSampler.implementation\">Java</stringProp>\n                  <stringProp name=\"HTTPSampler.connect_timeout\"></stringProp>\n                  <stringProp name=\"HTTPSampler.response_timeout\"></stringProp>\n                </HTTPSamplerProxy>\n                <hashTree>\n                  <ResponseAssertion guiclass=\"AssertionGui\" testclass=\"ResponseAssertion\" testname=\"Assertion - Response Contains Review of attempt 1\" enabled=\"false\">\n                    <collectionProp name=\"Asserion.test_strings\">\n                      <stringProp name=\"15650604\">Review of attempt</stringProp>\n                    </collectionProp>\n                    <intProp name=\"Assertion.test_type\">2</intProp>\n                    <stringProp name=\"Assertion.test_field\">Assertion.response_data</stringProp>\n                    <boolProp name=\"Assertion.assume_success\">false</boolProp>\n                    <stringProp name=\"Assertion.custom_message\"></stringProp>\n                  </ResponseAssertion>\n                  <hashTree/>\n                  <GaussianRandomTimer guiclass=\"GaussianRandomTimerGui\" testclass=\"GaussianRandomTimer\" testname=\"Gaussian Random Timer\" enabled=\"true\">\n                    <stringProp name=\"ConstantTimer.delay\">25000</stringProp>\n                    <stringProp name=\"RandomTimer.range\">15000</stringProp>\n                  </GaussianRandomTimer>\n                  <hashTree/>\n                  <ConstantTimer guiclass=\"ConstantTimerGui\" testclass=\"ConstantTimer\" testname=\"Constant Timer\" enabled=\"false\">\n                    <stringProp name=\"ConstantTimer.delay\">1000</stringProp>\n                  </ConstantTimer>\n                  <hashTree/>\n                  <ResultCollector guiclass=\"ViewResultsFullVisualizer\" testclass=\"ResultCollector\" testname=\"View Results Tree\" enabled=\"true\">\n                    <boolProp name=\"ResultCollector.error_logging\">false</boolProp>\n                    <objProp>\n                      <name>saveConfig</name>\n                      <value class=\"SampleSaveConfiguration\">\n                        <time>true</time>\n                        <latency>true</latency>\n                        <timestamp>true</timestamp>\n                        <success>true</success>\n                        <label>true</label>\n                        <code>true</code>\n                        <message>true</message>\n                        <threadName>true</threadName>\n                        <dataType>true</dataType>\n                        <encoding>false</encoding>\n                        <assertions>true</assertions>\n                        <subresults>true</subresults>\n                        <responseData>false</responseData>\n                        <samplerData>false</samplerData>\n                        <xml>false</xml>\n                        <fieldNames>true</fieldNames>\n                        <responseHeaders>false</responseHeaders>\n                        <requestHeaders>false</requestHeaders>\n                        <responseDataOnError>false</responseDataOnError>\n                        <saveAssertionResultsFailureMessage>true</saveAssertionResultsFailureMessage>\n                        <assertionsResultsToSave>0</assertionsResultsToSave>\n                        <bytes>true</bytes>\n                        <sentBytes>true</sentBytes>\n                        <threadCounts>true</threadCounts>\n                        <idleTime>true</idleTime>\n                        <connectTime>true</connectTime>\n                      </value>\n                    </objProp>\n                    <stringProp name=\"filename\"></stringProp>\n                  </ResultCollector>\n                  <hashTree/>\n                </hashTree>\n                <HTTPSamplerProxy guiclass=\"HttpTestSampleGui\" testclass=\"HTTPSamplerProxy\" testname=\"Submit all and finish\" enabled=\"true\">\n                  <elementProp name=\"HTTPsampler.Arguments\" elementType=\"Arguments\" guiclass=\"HTTPArgumentsPanel\" testclass=\"Arguments\" testname=\"Argument List\" enabled=\"true\">\n                    <collectionProp name=\"Arguments.arguments\">\n                      <elementProp name=\"sesskey\" elementType=\"HTTPArgument\">\n                        <boolProp name=\"HTTPArgument.always_encode\">false</boolProp>\n                        <stringProp name=\"Argument.value\">${sesskey}</stringProp>\n                        <stringProp name=\"Argument.metadata\">=</stringProp>\n                        <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                        <stringProp name=\"Argument.name\">sesskey</stringProp>\n                      </elementProp>\n                      <elementProp name=\"slots\" elementType=\"HTTPArgument\">\n                        <boolProp name=\"HTTPArgument.always_encode\">false</boolProp>\n                        <stringProp name=\"Argument.value\"></stringProp>\n                        <stringProp name=\"Argument.metadata\">=</stringProp>\n                        <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                        <stringProp name=\"Argument.name\">slots</stringProp>\n                      </elementProp>\n                      <elementProp name=\"timeup\" elementType=\"HTTPArgument\">\n                        <boolProp name=\"HTTPArgument.always_encode\">false</boolProp>\n                        <stringProp name=\"Argument.value\">0</stringProp>\n                        <stringProp name=\"Argument.metadata\">=</stringProp>\n                        <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                        <stringProp name=\"Argument.name\">timeup</stringProp>\n                      </elementProp>\n                      <elementProp name=\"finishattempt\" elementType=\"HTTPArgument\">\n                        <boolProp name=\"HTTPArgument.always_encode\">false</boolProp>\n                        <stringProp name=\"Argument.value\">1</stringProp>\n                        <stringProp name=\"Argument.metadata\">=</stringProp>\n                        <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                        <stringProp name=\"Argument.name\">finishattempt</stringProp>\n                      </elementProp>\n                      <elementProp name=\"attempt\" elementType=\"HTTPArgument\">\n                        <boolProp name=\"HTTPArgument.always_encode\">false</boolProp>\n                        <stringProp name=\"Argument.value\">${attemptid}</stringProp>\n                        <stringProp name=\"Argument.metadata\">=</stringProp>\n                        <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                        <stringProp name=\"Argument.name\">attempt</stringProp>\n                      </elementProp>\n                    </collectionProp>\n                  </elementProp>\n                  <stringProp name=\"HTTPSampler.domain\">${host}</stringProp>\n                  <stringProp name=\"HTTPSampler.port\">${port}</stringProp>\n                  <stringProp name=\"HTTPSampler.protocol\">${protocol}</stringProp>\n                  <stringProp name=\"HTTPSampler.contentEncoding\">utf-8</stringProp>\n                  <stringProp name=\"HTTPSampler.path\">/mod/quiz/processattempt.php</stringProp>\n                  <stringProp name=\"HTTPSampler.method\">POST</stringProp>\n                  <boolProp name=\"HTTPSampler.follow_redirects\">true</boolProp>\n                  <boolProp name=\"HTTPSampler.auto_redirects\">false</boolProp>\n                  <boolProp name=\"HTTPSampler.use_keepalive\">true</boolProp>\n                  <boolProp name=\"HTTPSampler.DO_MULTIPART_POST\">true</boolProp>\n                  <stringProp name=\"HTTPSampler.embedded_url_re\"></stringProp>\n                  <stringProp name=\"HTTPSampler.implementation\">Java</stringProp>\n                  <stringProp name=\"HTTPSampler.connect_timeout\"></stringProp>\n                  <stringProp name=\"HTTPSampler.response_timeout\"></stringProp>\n                </HTTPSamplerProxy>\n                <hashTree>\n                  <ResponseAssertion guiclass=\"AssertionGui\" testclass=\"ResponseAssertion\" testname=\"Assertion - Response Contains Review of attempt 1\" enabled=\"false\">\n                    <collectionProp name=\"Asserion.test_strings\">\n                      <stringProp name=\"15650604\">Review of attempt</stringProp>\n                    </collectionProp>\n                    <intProp name=\"Assertion.test_type\">2</intProp>\n                    <stringProp name=\"Assertion.test_field\">Assertion.response_data</stringProp>\n                    <boolProp name=\"Assertion.assume_success\">false</boolProp>\n                    <stringProp name=\"Assertion.custom_message\"></stringProp>\n                  </ResponseAssertion>\n                  <hashTree/>\n                  <GaussianRandomTimer guiclass=\"GaussianRandomTimerGui\" testclass=\"GaussianRandomTimer\" testname=\"Gaussian Random Timer\" enabled=\"true\">\n                    <stringProp name=\"ConstantTimer.delay\">4000</stringProp>\n                    <stringProp name=\"RandomTimer.range\">3000</stringProp>\n                  </GaussianRandomTimer>\n                  <hashTree/>\n                  <ConstantTimer guiclass=\"ConstantTimerGui\" testclass=\"ConstantTimer\" testname=\"Constant Timer\" enabled=\"false\">\n                    <stringProp name=\"ConstantTimer.delay\">1000</stringProp>\n                  </ConstantTimer>\n                  <hashTree/>\n                  <ResultCollector guiclass=\"ViewResultsFullVisualizer\" testclass=\"ResultCollector\" testname=\"View Results Tree\" enabled=\"false\">\n                    <boolProp name=\"ResultCollector.error_logging\">false</boolProp>\n                    <objProp>\n                      <name>saveConfig</name>\n                      <value class=\"SampleSaveConfiguration\">\n                        <time>true</time>\n                        <latency>true</latency>\n                        <timestamp>true</timestamp>\n                        <success>true</success>\n                        <label>true</label>\n                        <code>true</code>\n                        <message>true</message>\n                        <threadName>true</threadName>\n                        <dataType>true</dataType>\n                        <encoding>false</encoding>\n                        <assertions>true</assertions>\n                        <subresults>true</subresults>\n                        <responseData>false</responseData>\n                        <samplerData>false</samplerData>\n                        <xml>false</xml>\n                        <fieldNames>true</fieldNames>\n                        <responseHeaders>false</responseHeaders>\n                        <requestHeaders>false</requestHeaders>\n                        <responseDataOnError>false</responseDataOnError>\n                        <saveAssertionResultsFailureMessage>true</saveAssertionResultsFailureMessage>\n                        <assertionsResultsToSave>0</assertionsResultsToSave>\n                        <bytes>true</bytes>\n                        <sentBytes>true</sentBytes>\n                        <threadCounts>true</threadCounts>\n                        <idleTime>true</idleTime>\n                        <connectTime>true</connectTime>\n                      </value>\n                    </objProp>\n                    <stringProp name=\"filename\"></stringProp>\n                  </ResultCollector>\n                  <hashTree/>\n                </hashTree>\n                <HTTPSamplerProxy guiclass=\"HttpTestSampleGui\" testclass=\"HTTPSamplerProxy\" testname=\"Click Finish Review\" enabled=\"true\">\n                  <elementProp name=\"HTTPsampler.Arguments\" elementType=\"Arguments\" guiclass=\"HTTPArgumentsPanel\" testclass=\"Arguments\" testname=\"Argument List\" enabled=\"true\">\n                    <collectionProp name=\"Arguments.arguments\">\n                      <elementProp name=\"id\" elementType=\"HTTPArgument\">\n                        <boolProp name=\"HTTPArgument.always_encode\">false</boolProp>\n                        <stringProp name=\"Argument.value\">${moduleid}</stringProp>\n                        <stringProp name=\"Argument.metadata\">=</stringProp>\n                        <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                        <stringProp name=\"Argument.name\">id</stringProp>\n                      </elementProp>\n                    </collectionProp>\n                  </elementProp>\n                  <stringProp name=\"HTTPSampler.domain\">${host}</stringProp>\n                  <stringProp name=\"HTTPSampler.port\">${port}</stringProp>\n                  <stringProp name=\"HTTPSampler.protocol\">${protocol}</stringProp>\n                  <stringProp name=\"HTTPSampler.contentEncoding\"></stringProp>\n                  <stringProp name=\"HTTPSampler.path\">/mod/quiz/view.php</stringProp>\n                  <stringProp name=\"HTTPSampler.method\">GET</stringProp>\n                  <boolProp name=\"HTTPSampler.follow_redirects\">true</boolProp>\n                  <boolProp name=\"HTTPSampler.auto_redirects\">false</boolProp>\n                  <boolProp name=\"HTTPSampler.use_keepalive\">true</boolProp>\n                  <boolProp name=\"HTTPSampler.DO_MULTIPART_POST\">true</boolProp>\n                  <stringProp name=\"HTTPSampler.embedded_url_re\"></stringProp>\n                  <stringProp name=\"HTTPSampler.implementation\">Java</stringProp>\n                  <stringProp name=\"HTTPSampler.connect_timeout\"></stringProp>\n                  <stringProp name=\"HTTPSampler.response_timeout\"></stringProp>\n                </HTTPSamplerProxy>\n                <hashTree>\n                  <ResponseAssertion guiclass=\"AssertionGui\" testclass=\"ResponseAssertion\" testname=\"Assertion - Response Contains Summary of your previous atte...\" enabled=\"false\">\n                    <collectionProp name=\"Asserion.test_strings\">\n                      <stringProp name=\"1813422065\">Summary of your previous attempts</stringProp>\n                    </collectionProp>\n                    <intProp name=\"Assertion.test_type\">2</intProp>\n                    <stringProp name=\"Assertion.test_field\">Assertion.response_data</stringProp>\n                    <boolProp name=\"Assertion.assume_success\">false</boolProp>\n                    <stringProp name=\"Assertion.custom_message\"></stringProp>\n                  </ResponseAssertion>\n                  <hashTree/>\n                  <ResponseAssertion guiclass=\"AssertionGui\" testclass=\"ResponseAssertion\" testname=\"Assertion - Response Contains Highest grade\" enabled=\"false\">\n                    <collectionProp name=\"Asserion.test_strings\">\n                      <stringProp name=\"-1064949445\">Highest grade</stringProp>\n                    </collectionProp>\n                    <intProp name=\"Assertion.test_type\">2</intProp>\n                    <stringProp name=\"Assertion.test_field\">Assertion.response_data</stringProp>\n                    <boolProp name=\"Assertion.assume_success\">false</boolProp>\n                    <stringProp name=\"Assertion.custom_message\"></stringProp>\n                  </ResponseAssertion>\n                  <hashTree/>\n                  <GaussianRandomTimer guiclass=\"GaussianRandomTimerGui\" testclass=\"GaussianRandomTimer\" testname=\"Gaussian Random Timer\" enabled=\"true\">\n                    <stringProp name=\"ConstantTimer.delay\">4000</stringProp>\n                    <stringProp name=\"RandomTimer.range\">3000</stringProp>\n                  </GaussianRandomTimer>\n                  <hashTree/>\n                  <ConstantTimer guiclass=\"ConstantTimerGui\" testclass=\"ConstantTimer\" testname=\"Constant Timer\" enabled=\"false\">\n                    <stringProp name=\"ConstantTimer.delay\">1000</stringProp>\n                  </ConstantTimer>\n                  <hashTree/>\n                </hashTree>\n                <DebugSampler guiclass=\"TestBeanGUI\" testclass=\"DebugSampler\" testname=\"Debug Sampler\" enabled=\"false\">\n                  <boolProp name=\"displayJMeterProperties\">false</boolProp>\n                  <boolProp name=\"displayJMeterVariables\">true</boolProp>\n                  <boolProp name=\"displaySystemProperties\">false</boolProp>\n                </DebugSampler>\n                <hashTree/>\n                <ResultCollector guiclass=\"GraphVisualizer\" testclass=\"ResultCollector\" testname=\"Graph Results\" enabled=\"false\">\n                  <boolProp name=\"ResultCollector.error_logging\">false</boolProp>\n                  <objProp>\n                    <name>saveConfig</name>\n                    <value class=\"SampleSaveConfiguration\">\n                      <time>true</time>\n                      <latency>true</latency>\n                      <timestamp>true</timestamp>\n                      <success>true</success>\n                      <label>true</label>\n                      <code>true</code>\n                      <message>true</message>\n                      <threadName>true</threadName>\n                      <dataType>true</dataType>\n                      <encoding>false</encoding>\n                      <assertions>true</assertions>\n                      <subresults>true</subresults>\n                      <responseData>false</responseData>\n                      <samplerData>false</samplerData>\n                      <xml>true</xml>\n                      <fieldNames>false</fieldNames>\n                      <responseHeaders>false</responseHeaders>\n                      <requestHeaders>false</requestHeaders>\n                      <responseDataOnError>false</responseDataOnError>\n                      <saveAssertionResultsFailureMessage>false</saveAssertionResultsFailureMessage>\n                      <assertionsResultsToSave>0</assertionsResultsToSave>\n                      <bytes>true</bytes>\n                      <threadCounts>true</threadCounts>\n                    </value>\n                  </objProp>\n                  <stringProp name=\"filename\"></stringProp>\n                </ResultCollector>\n                <hashTree/>\n                <ResultCollector guiclass=\"ViewResultsFullVisualizer\" testclass=\"ResultCollector\" testname=\"View Results Tree\" enabled=\"false\">\n                  <boolProp name=\"ResultCollector.error_logging\">false</boolProp>\n                  <objProp>\n                    <name>saveConfig</name>\n                    <value class=\"SampleSaveConfiguration\">\n                      <time>true</time>\n                      <latency>true</latency>\n                      <timestamp>true</timestamp>\n                      <success>true</success>\n                      <label>true</label>\n                      <code>true</code>\n                      <message>true</message>\n                      <threadName>true</threadName>\n                      <dataType>true</dataType>\n                      <encoding>false</encoding>\n                      <assertions>true</assertions>\n                      <subresults>true</subresults>\n                      <responseData>false</responseData>\n                      <samplerData>false</samplerData>\n                      <xml>false</xml>\n                      <fieldNames>false</fieldNames>\n                      <responseHeaders>false</responseHeaders>\n                      <requestHeaders>false</requestHeaders>\n                      <responseDataOnError>false</responseDataOnError>\n                      <saveAssertionResultsFailureMessage>false</saveAssertionResultsFailureMessage>\n                      <assertionsResultsToSave>0</assertionsResultsToSave>\n                      <bytes>true</bytes>\n                      <threadCounts>true</threadCounts>\n                    </value>\n                  </objProp>\n                  <stringProp name=\"filename\"></stringProp>\n                </ResultCollector>\n                <hashTree/>\n                <GaussianRandomTimer guiclass=\"GaussianRandomTimerGui\" testclass=\"GaussianRandomTimer\" testname=\"Gaussian Random Timer\" enabled=\"true\">\n                  <stringProp name=\"ConstantTimer.delay\">20000</stringProp>\n                  <stringProp name=\"RandomTimer.range\">20000</stringProp>\n                </GaussianRandomTimer>\n                <hashTree/>\n              </hashTree>\n            </hashTree>\n          </hashTree>\n          <ResultCollector guiclass=\"TableVisualizer\" testclass=\"ResultCollector\" testname=\"View Results in Table\" enabled=\"true\">\n            <boolProp name=\"ResultCollector.error_logging\">false</boolProp>\n            <objProp>\n              <name>saveConfig</name>\n              <value class=\"SampleSaveConfiguration\">\n                <time>true</time>\n                <latency>true</latency>\n                <timestamp>true</timestamp>\n                <success>true</success>\n                <label>true</label>\n                <code>true</code>\n                <message>true</message>\n                <threadName>true</threadName>\n                <dataType>true</dataType>\n                <encoding>false</encoding>\n                <assertions>true</assertions>\n                <subresults>true</subresults>\n                <responseData>false</responseData>\n                <samplerData>false</samplerData>\n                <xml>false</xml>\n                <fieldNames>true</fieldNames>\n                <responseHeaders>false</responseHeaders>\n                <requestHeaders>false</requestHeaders>\n                <responseDataOnError>false</responseDataOnError>\n                <saveAssertionResultsFailureMessage>true</saveAssertionResultsFailureMessage>\n                <assertionsResultsToSave>0</assertionsResultsToSave>\n                <bytes>true</bytes>\n                <sentBytes>true</sentBytes>\n                <threadCounts>true</threadCounts>\n                <idleTime>true</idleTime>\n                <connectTime>true</connectTime>\n              </value>\n            </objProp>\n            <stringProp name=\"filename\"></stringProp>\n          </ResultCollector>\n          <hashTree/>\n          <ResultCollector guiclass=\"SummaryReport\" testclass=\"ResultCollector\" testname=\"Summary Report\" enabled=\"true\">\n            <boolProp name=\"ResultCollector.error_logging\">false</boolProp>\n            <objProp>\n              <name>saveConfig</name>\n              <value class=\"SampleSaveConfiguration\">\n                <time>true</time>\n                <latency>true</latency>\n                <timestamp>true</timestamp>\n                <success>true</success>\n                <label>true</label>\n                <code>true</code>\n                <message>true</message>\n                <threadName>true</threadName>\n                <dataType>true</dataType>\n                <encoding>false</encoding>\n                <assertions>true</assertions>\n                <subresults>true</subresults>\n                <responseData>false</responseData>\n                <samplerData>false</samplerData>\n                <xml>false</xml>\n                <fieldNames>true</fieldNames>\n                <responseHeaders>false</responseHeaders>\n                <requestHeaders>false</requestHeaders>\n                <responseDataOnError>false</responseDataOnError>\n                <saveAssertionResultsFailureMessage>true</saveAssertionResultsFailureMessage>\n                <assertionsResultsToSave>0</assertionsResultsToSave>\n                <bytes>true</bytes>\n                <sentBytes>true</sentBytes>\n                <threadCounts>true</threadCounts>\n                <idleTime>true</idleTime>\n                <connectTime>true</connectTime>\n              </value>\n            </objProp>\n            <stringProp name=\"filename\"></stringProp>\n          </ResultCollector>\n          <hashTree/>\n          <kg.apc.jmeter.vizualizers.CorrectedResultCollector guiclass=\"kg.apc.jmeter.vizualizers.HitsPerSecondGui\" testclass=\"kg.apc.jmeter.vizualizers.CorrectedResultCollector\" testname=\"jp@gc - Hits per Second\" enabled=\"true\">\n            <boolProp name=\"ResultCollector.error_logging\">false</boolProp>\n            <objProp>\n              <name>saveConfig</name>\n              <value class=\"SampleSaveConfiguration\">\n                <time>true</time>\n                <latency>true</latency>\n                <timestamp>true</timestamp>\n                <success>true</success>\n                <label>true</label>\n                <code>true</code>\n                <message>true</message>\n                <threadName>true</threadName>\n                <dataType>true</dataType>\n                <encoding>false</encoding>\n                <assertions>true</assertions>\n                <subresults>true</subresults>\n                <responseData>false</responseData>\n                <samplerData>false</samplerData>\n                <xml>false</xml>\n                <fieldNames>true</fieldNames>\n                <responseHeaders>false</responseHeaders>\n                <requestHeaders>false</requestHeaders>\n                <responseDataOnError>false</responseDataOnError>\n                <saveAssertionResultsFailureMessage>true</saveAssertionResultsFailureMessage>\n                <assertionsResultsToSave>0</assertionsResultsToSave>\n                <bytes>true</bytes>\n                <sentBytes>true</sentBytes>\n                <threadCounts>true</threadCounts>\n                <idleTime>true</idleTime>\n                <connectTime>true</connectTime>\n              </value>\n            </objProp>\n            <stringProp name=\"filename\"></stringProp>\n            <longProp name=\"interval_grouping\">1000</longProp>\n            <boolProp name=\"graph_aggregated\">false</boolProp>\n            <stringProp name=\"include_sample_labels\"></stringProp>\n            <stringProp name=\"exclude_sample_labels\"></stringProp>\n            <stringProp name=\"start_offset\"></stringProp>\n            <stringProp name=\"end_offset\"></stringProp>\n            <boolProp name=\"include_checkbox_state\">false</boolProp>\n            <boolProp name=\"exclude_checkbox_state\">false</boolProp>\n          </kg.apc.jmeter.vizualizers.CorrectedResultCollector>\n          <hashTree/>\n          <kg.apc.jmeter.vizualizers.CorrectedResultCollector guiclass=\"kg.apc.jmeter.vizualizers.ResponseCodesPerSecondGui\" testclass=\"kg.apc.jmeter.vizualizers.CorrectedResultCollector\" testname=\"jp@gc - Response Codes per Second\" enabled=\"true\">\n            <boolProp name=\"ResultCollector.error_logging\">false</boolProp>\n            <objProp>\n              <name>saveConfig</name>\n              <value class=\"SampleSaveConfiguration\">\n                <time>true</time>\n                <latency>true</latency>\n                <timestamp>true</timestamp>\n                <success>true</success>\n                <label>true</label>\n                <code>true</code>\n                <message>true</message>\n                <threadName>true</threadName>\n                <dataType>true</dataType>\n                <encoding>false</encoding>\n                <assertions>true</assertions>\n                <subresults>true</subresults>\n                <responseData>false</responseData>\n                <samplerData>false</samplerData>\n                <xml>false</xml>\n                <fieldNames>true</fieldNames>\n                <responseHeaders>false</responseHeaders>\n                <requestHeaders>false</requestHeaders>\n                <responseDataOnError>false</responseDataOnError>\n                <saveAssertionResultsFailureMessage>true</saveAssertionResultsFailureMessage>\n                <assertionsResultsToSave>0</assertionsResultsToSave>\n                <bytes>true</bytes>\n                <sentBytes>true</sentBytes>\n                <threadCounts>true</threadCounts>\n                <idleTime>true</idleTime>\n                <connectTime>true</connectTime>\n              </value>\n            </objProp>\n            <stringProp name=\"filename\"></stringProp>\n            <longProp name=\"interval_grouping\">1000</longProp>\n            <boolProp name=\"graph_aggregated\">false</boolProp>\n            <stringProp name=\"include_sample_labels\"></stringProp>\n            <stringProp name=\"exclude_sample_labels\"></stringProp>\n            <stringProp name=\"start_offset\"></stringProp>\n            <stringProp name=\"end_offset\"></stringProp>\n            <boolProp name=\"include_checkbox_state\">false</boolProp>\n            <boolProp name=\"exclude_checkbox_state\">false</boolProp>\n          </kg.apc.jmeter.vizualizers.CorrectedResultCollector>\n          <hashTree/>\n          <kg.apc.jmeter.vizualizers.CorrectedResultCollector guiclass=\"kg.apc.jmeter.vizualizers.LatenciesOverTimeGui\" testclass=\"kg.apc.jmeter.vizualizers.CorrectedResultCollector\" testname=\"jp@gc - Response Latencies Over Time\" enabled=\"true\">\n            <boolProp name=\"ResultCollector.error_logging\">false</boolProp>\n            <objProp>\n              <name>saveConfig</name>\n              <value class=\"SampleSaveConfiguration\">\n                <time>true</time>\n                <latency>true</latency>\n                <timestamp>true</timestamp>\n                <success>true</success>\n                <label>true</label>\n                <code>true</code>\n                <message>true</message>\n                <threadName>true</threadName>\n                <dataType>true</dataType>\n                <encoding>false</encoding>\n                <assertions>true</assertions>\n                <subresults>true</subresults>\n                <responseData>false</responseData>\n                <samplerData>false</samplerData>\n                <xml>false</xml>\n                <fieldNames>true</fieldNames>\n                <responseHeaders>false</responseHeaders>\n                <requestHeaders>false</requestHeaders>\n                <responseDataOnError>false</responseDataOnError>\n                <saveAssertionResultsFailureMessage>true</saveAssertionResultsFailureMessage>\n                <assertionsResultsToSave>0</assertionsResultsToSave>\n                <bytes>true</bytes>\n                <sentBytes>true</sentBytes>\n                <threadCounts>true</threadCounts>\n                <idleTime>true</idleTime>\n                <connectTime>true</connectTime>\n              </value>\n            </objProp>\n            <stringProp name=\"filename\"></stringProp>\n            <longProp name=\"interval_grouping\">500</longProp>\n            <boolProp name=\"graph_aggregated\">false</boolProp>\n            <stringProp name=\"include_sample_labels\"></stringProp>\n            <stringProp name=\"exclude_sample_labels\"></stringProp>\n            <stringProp name=\"start_offset\"></stringProp>\n            <stringProp name=\"end_offset\"></stringProp>\n            <boolProp name=\"include_checkbox_state\">false</boolProp>\n            <boolProp name=\"exclude_checkbox_state\">false</boolProp>\n          </kg.apc.jmeter.vizualizers.CorrectedResultCollector>\n          <hashTree/>\n        </hashTree>\n      </hashTree>\n    </hashTree>\n  </hashTree>\n</jmeterTestPlan>\n"
  },
  {
    "path": "loadtest/simple-test-2.jmx",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<jmeterTestPlan version=\"1.2\" properties=\"4.0\" jmeter=\"4.0 r1823414\">\n  <hashTree>\n    <TestPlan guiclass=\"TestPlanGui\" testclass=\"TestPlan\" testname=\"Moodle Load Test Plan\" enabled=\"true\">\n      <boolProp name=\"TestPlan.functional_mode\">false</boolProp>\n      <collectionProp name=\"TestPlan.thread_groups\"/>\n      <elementProp name=\"TestPlan.user_defined_variables\" elementType=\"Arguments\" guiclass=\"ArgumentsPanel\" testclass=\"Arguments\" testname=\"Argument List\" enabled=\"true\">\n        <collectionProp name=\"Arguments.arguments\"/>\n      </elementProp>\n      <boolProp name=\"TestPlan.serialize_threadgroups\">false</boolProp>\n      <stringProp name=\"TestPlan.comments\"></stringProp>\n      <stringProp name=\"TestPlan.user_define_classpath\"></stringProp>\n    </TestPlan>\n    <hashTree>\n      <Arguments guiclass=\"ArgumentsPanel\" testclass=\"Arguments\" testname=\"User Defined Variables\" enabled=\"true\">\n        <collectionProp name=\"Arguments.arguments\">\n          <elementProp name=\"login_host\" elementType=\"Argument\">\n            <stringProp name=\"Argument.name\">login_host</stringProp>\n            <stringProp name=\"Argument.value\">${__P(host, lb-hivmol.southcentralus.cloudapp.azure.com)}</stringProp>\n            <stringProp name=\"Argument.metadata\">=</stringProp>\n          </elementProp>\n          <elementProp name=\"host\" elementType=\"Argument\">\n            <stringProp name=\"Argument.name\">host</stringProp>\n            <stringProp name=\"Argument.value\">${__P(host, lb-hivmol.southcentralus.cloudapp.azure.com)}</stringProp>\n            <stringProp name=\"Argument.metadata\">=</stringProp>\n          </elementProp>\n          <elementProp name=\"threads\" elementType=\"Argument\">\n            <stringProp name=\"Argument.name\">threads</stringProp>\n            <stringProp name=\"Argument.value\">${__P(threads,100)}</stringProp>\n            <stringProp name=\"Argument.metadata\">=</stringProp>\n            <stringProp name=\"Argument.desc\">Default 10 (for a quick GUI run)</stringProp>\n          </elementProp>\n          <elementProp name=\"rampup\" elementType=\"Argument\">\n            <stringProp name=\"Argument.name\">rampup</stringProp>\n            <stringProp name=\"Argument.value\">${__P(rampup,1200)}</stringProp>\n            <stringProp name=\"Argument.metadata\">=</stringProp>\n            <stringProp name=\"Argument.desc\">Default 120 - 1/15 runtime default rampup</stringProp>\n          </elementProp>\n          <elementProp name=\"runtime\" elementType=\"Argument\">\n            <stringProp name=\"Argument.name\">runtime</stringProp>\n            <stringProp name=\"Argument.value\">${__P(runtime,86400)}</stringProp>\n            <stringProp name=\"Argument.metadata\">=</stringProp>\n            <stringProp name=\"Argument.desc\">Default 1800 = 30 minutes = 30*60 seconds</stringProp>\n          </elementProp>\n          <elementProp name=\"default_connect_timeout\" elementType=\"Argument\">\n            <stringProp name=\"Argument.name\">default_connect_timeout</stringProp>\n            <stringProp name=\"Argument.value\">2000</stringProp>\n            <stringProp name=\"Argument.metadata\">=</stringProp>\n            <stringProp name=\"Argument.desc\">1.2 seconds to a TCP ack</stringProp>\n          </elementProp>\n          <elementProp name=\"default_response_timeout\" elementType=\"Argument\">\n            <stringProp name=\"Argument.name\">default_response_timeout</stringProp>\n            <stringProp name=\"Argument.value\">240000</stringProp>\n            <stringProp name=\"Argument.metadata\">=</stringProp>\n            <stringProp name=\"Argument.desc\">4 Minutes for the page to respond</stringProp>\n          </elementProp>\n          <elementProp name=\"throughput_rate\" elementType=\"Argument\">\n            <stringProp name=\"Argument.name\">throughput_rate</stringProp>\n            <stringProp name=\"Argument.value\">${__P(throughput_rate, 50)}</stringProp>\n            <stringProp name=\"Argument.metadata\">=</stringProp>\n            <stringProp name=\"Argument.desc\">Default 650 should approximate ~ 7k page views / 5 mins cacti sample</stringProp>\n          </elementProp>\n          <elementProp name=\"activity_course_id\" elementType=\"Argument\">\n            <stringProp name=\"Argument.name\">activity_course_id</stringProp>\n            <stringProp name=\"Argument.value\">${__P(activity_course_id, 2)}</stringProp>\n            <stringProp name=\"Argument.metadata\">=</stringProp>\n            <stringProp name=\"Argument.desc\">This must match the course ID of the course with performance activities</stringProp>\n          </elementProp>\n          <elementProp name=\"quiz_delay\" elementType=\"Argument\">\n            <stringProp name=\"Argument.name\">quiz_delay</stringProp>\n            <stringProp name=\"Argument.value\">${__P(quiz_delay, 100)}</stringProp>\n            <stringProp name=\"Argument.desc\">-1 means don&apos;t run quiz</stringProp>\n            <stringProp name=\"Argument.metadata\">=</stringProp>\n          </elementProp>\n          <elementProp name=\"quiz_users\" elementType=\"Argument\">\n            <stringProp name=\"Argument.name\">quiz_users</stringProp>\n            <stringProp name=\"Argument.value\">${__P(quiz_users, 1)}</stringProp>\n            <stringProp name=\"Argument.desc\">Number of quiz users to simulate</stringProp>\n            <stringProp name=\"Argument.metadata\">=</stringProp>\n          </elementProp>\n          <elementProp name=\"groupselect_delay\" elementType=\"Argument\">\n            <stringProp name=\"Argument.name\">groupselect_delay</stringProp>\n            <stringProp name=\"Argument.value\">${__P(groupselect_delay,-1)}</stringProp>\n            <stringProp name=\"Argument.metadata\">=</stringProp>\n          </elementProp>\n          <elementProp name=\"groupselect_users\" elementType=\"Argument\">\n            <stringProp name=\"Argument.name\">groupselect_users</stringProp>\n            <stringProp name=\"Argument.value\">${__P(groupselect_users, 0)}</stringProp>\n            <stringProp name=\"Argument.metadata\">=</stringProp>\n          </elementProp>\n          <elementProp name=\"protocol\" elementType=\"Argument\">\n            <stringProp name=\"Argument.name\">protocol</stringProp>\n            <stringProp name=\"Argument.value\">https</stringProp>\n            <stringProp name=\"Argument.metadata\">=</stringProp>\n          </elementProp>\n          <elementProp name=\"port\" elementType=\"Argument\">\n            <stringProp name=\"Argument.name\">port</stringProp>\n            <stringProp name=\"Argument.value\">443</stringProp>\n            <stringProp name=\"Argument.metadata\">=</stringProp>\n          </elementProp>\n          <elementProp name=\"moodle_user_pass\" elementType=\"Argument\">\n            <stringProp name=\"Argument.name\">moodle_user_pass</stringProp>\n            <stringProp name=\"Argument.value\">${__P(moodle_user_pass,testUserP@$$w0rd)}</stringProp>\n            <stringProp name=\"Argument.metadata\">=</stringProp>\n          </elementProp>\n        </collectionProp>\n      </Arguments>\n      <hashTree/>\n      <ThreadGroup guiclass=\"ThreadGroupGui\" testclass=\"ThreadGroup\" testname=\"Load Test Moodle\" enabled=\"true\">\n        <stringProp name=\"ThreadGroup.on_sample_error\">startnextloop</stringProp>\n        <elementProp name=\"ThreadGroup.main_controller\" elementType=\"LoopController\" guiclass=\"LoopControlPanel\" testclass=\"LoopController\" testname=\"Loop Controller\" enabled=\"true\">\n          <boolProp name=\"LoopController.continue_forever\">false</boolProp>\n          <intProp name=\"LoopController.loops\">-1</intProp>\n        </elementProp>\n        <stringProp name=\"ThreadGroup.num_threads\">${threads}</stringProp>\n        <stringProp name=\"ThreadGroup.ramp_time\">${rampup}</stringProp>\n        <longProp name=\"ThreadGroup.start_time\">1513564238000</longProp>\n        <longProp name=\"ThreadGroup.end_time\">1513564538000</longProp>\n        <boolProp name=\"ThreadGroup.scheduler\">true</boolProp>\n        <stringProp name=\"ThreadGroup.duration\">${runtime}</stringProp>\n        <stringProp name=\"ThreadGroup.delay\">0</stringProp>\n      </ThreadGroup>\n      <hashTree>\n        <CookieManager guiclass=\"CookiePanel\" testclass=\"CookieManager\" testname=\"HTTP Cookie Manager\" enabled=\"true\">\n          <collectionProp name=\"CookieManager.cookies\"/>\n          <boolProp name=\"CookieManager.clearEachIteration\">true</boolProp>\n          <stringProp name=\"CookieManager.policy\">default</stringProp>\n        </CookieManager>\n        <hashTree/>\n        <GenericController guiclass=\"LogicControllerGui\" testclass=\"GenericController\" testname=\"Moodle Tests\" enabled=\"true\"/>\n        <hashTree>\n          <HeaderManager guiclass=\"HeaderPanel\" testclass=\"HeaderManager\" testname=\"HTTP Header Manager\" enabled=\"true\">\n            <collectionProp name=\"HeaderManager.headers\">\n              <elementProp name=\"\" elementType=\"Header\">\n                <stringProp name=\"Header.name\">User-Agent</stringProp>\n                <stringProp name=\"Header.value\">Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0; Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1) ; InfoPath.1; .NET CLR 1.1.4322; .NET CLR 2.0.50727; .NET CLR 3.0.04506.648; .NET CLR 3.5.21022; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729; .NET4.0C; OfficeLiveConnector.1.5; OfficeLivePatch.1.3)</stringProp>\n              </elementProp>\n              <elementProp name=\"\" elementType=\"Header\">\n                <stringProp name=\"Header.name\">Accept</stringProp>\n                <stringProp name=\"Header.value\">*/*</stringProp>\n              </elementProp>\n              <elementProp name=\"\" elementType=\"Header\">\n                <stringProp name=\"Header.name\">Accept-Language</stringProp>\n                <stringProp name=\"Header.value\">en-us</stringProp>\n              </elementProp>\n            </collectionProp>\n          </HeaderManager>\n          <hashTree/>\n          <GenericController guiclass=\"LogicControllerGui\" testclass=\"GenericController\" testname=\"Moodle tests\" enabled=\"true\"/>\n          <hashTree>\n            <UserParameters guiclass=\"UserParametersGui\" testclass=\"UserParameters\" testname=\"User Parameters\" enabled=\"true\">\n              <boolProp name=\"TestElement.per_iteration\">true</boolProp>\n              <collectionProp name=\"UserParameters.names\">\n                <stringProp name=\"VIEWSTATE\">VIEWSTATE</stringProp>\n                <stringProp name=\"jsessionid\">jsessionid</stringProp>\n                <stringProp name=\"-532667892\">RandomNumber</stringProp>\n                <stringProp name=\"-265713450\">username</stringProp>\n                <stringProp name=\"-1797703761\">userRandomChoice</stringProp>\n                <stringProp name=\"1984989421\">sesskey</stringProp>\n                <stringProp name=\"775281475\">discussionid</stringProp>\n                <stringProp name=\"-1804370087\">courseList</stringProp>\n                <stringProp name=\"-2078389680\">xx_outputFromSplit</stringProp>\n              </collectionProp>\n              <collectionProp name=\"UserParameters.thread_values\">\n                <collectionProp name=\"-2095354106\">\n                  <stringProp name=\"\"></stringProp>\n                  <stringProp name=\"\"></stringProp>\n                  <stringProp name=\"-2046088625\">${__Random(1,2000,ReuseRandomNumber)}</stringProp>\n                  <stringProp name=\"379755733\">m_azuretestuser_${RandomNumber}</stringProp>\n                  <stringProp name=\"1345253498\">${RandomNumber}</stringProp>\n                  <stringProp name=\"-352988409\">${sesskey}</stringProp>\n                  <stringProp name=\"0\"></stringProp>\n                  <stringProp name=\"257720200\">${__V(course_list_${RandomNumber})}</stringProp>\n                  <stringProp name=\"-306911129\">${__split(${courseList}, course_id,|)}</stringProp>\n                </collectionProp>\n              </collectionProp>\n              <boolProp name=\"UserParameters.per_iteration\">true</boolProp>\n            </UserParameters>\n            <hashTree/>\n            <HTTPSamplerProxy guiclass=\"HttpTestSampleGui\" testclass=\"HTTPSamplerProxy\" testname=\"Goto Moodle\" enabled=\"true\">\n              <elementProp name=\"HTTPsampler.Arguments\" elementType=\"Arguments\" guiclass=\"HTTPArgumentsPanel\" testclass=\"Arguments\" testname=\"Argument List\" enabled=\"true\">\n                <collectionProp name=\"Arguments.arguments\"/>\n              </elementProp>\n              <stringProp name=\"HTTPSampler.domain\">${host}</stringProp>\n              <stringProp name=\"HTTPSampler.port\">${port}</stringProp>\n              <stringProp name=\"HTTPSampler.protocol\">${protocol}</stringProp>\n              <stringProp name=\"HTTPSampler.contentEncoding\"></stringProp>\n              <stringProp name=\"HTTPSampler.path\">/</stringProp>\n              <stringProp name=\"HTTPSampler.method\">GET</stringProp>\n              <boolProp name=\"HTTPSampler.follow_redirects\">true</boolProp>\n              <boolProp name=\"HTTPSampler.auto_redirects\">false</boolProp>\n              <boolProp name=\"HTTPSampler.use_keepalive\">true</boolProp>\n              <boolProp name=\"HTTPSampler.DO_MULTIPART_POST\">false</boolProp>\n              <stringProp name=\"HTTPSampler.embedded_url_re\"></stringProp>\n              <stringProp name=\"HTTPSampler.implementation\">Java</stringProp>\n              <stringProp name=\"HTTPSampler.connect_timeout\">${default_connect_timeout}</stringProp>\n              <stringProp name=\"HTTPSampler.response_timeout\">${default_response_timeout}</stringProp>\n            </HTTPSamplerProxy>\n            <hashTree>\n              <ConstantTimer guiclass=\"ConstantTimerGui\" testclass=\"ConstantTimer\" testname=\"Constant Timer\" enabled=\"false\">\n                <stringProp name=\"ConstantTimer.delay\">1000</stringProp>\n              </ConstantTimer>\n              <hashTree/>\n              <GaussianRandomTimer guiclass=\"GaussianRandomTimerGui\" testclass=\"GaussianRandomTimer\" testname=\"Gaussian Random Timer\" enabled=\"true\">\n                <stringProp name=\"ConstantTimer.delay\">8000</stringProp>\n                <stringProp name=\"RandomTimer.range\">4000</stringProp>\n              </GaussianRandomTimer>\n              <hashTree/>\n            </hashTree>\n            <HTTPSamplerProxy guiclass=\"HttpTestSampleGui\" testclass=\"HTTPSamplerProxy\" testname=\"POST login\" enabled=\"true\">\n              <elementProp name=\"HTTPsampler.Arguments\" elementType=\"Arguments\" guiclass=\"HTTPArgumentsPanel\" testclass=\"Arguments\" testname=\"Argument List\" enabled=\"true\">\n                <collectionProp name=\"Arguments.arguments\">\n                  <elementProp name=\"username\" elementType=\"HTTPArgument\">\n                    <stringProp name=\"HTTPArgument.always_encode\">true</stringProp>\n                    <stringProp name=\"Argument.value\">${username}</stringProp>\n                    <stringProp name=\"Argument.name\">username</stringProp>\n                    <stringProp name=\"Argument.metadata\">=</stringProp>\n                    <stringProp name=\"Argument.use_equals\">true</stringProp>\n                    <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                  </elementProp>\n                  <elementProp name=\"password\" elementType=\"HTTPArgument\">\n                    <stringProp name=\"HTTPArgument.always_encode\">true</stringProp>\n                    <stringProp name=\"Argument.value\">${moodle_user_pass}</stringProp>\n                    <stringProp name=\"Argument.name\">password</stringProp>\n                    <stringProp name=\"Argument.metadata\">=</stringProp>\n                    <stringProp name=\"Argument.use_equals\">true</stringProp>\n                    <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                  </elementProp>\n                  <elementProp name=\"Login\" elementType=\"HTTPArgument\">\n                    <boolProp name=\"HTTPArgument.always_encode\">true</boolProp>\n                    <stringProp name=\"Argument.value\">Login</stringProp>\n                    <stringProp name=\"Argument.metadata\">=</stringProp>\n                    <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                    <stringProp name=\"Argument.name\">Login</stringProp>\n                  </elementProp>\n                </collectionProp>\n              </elementProp>\n              <stringProp name=\"HTTPSampler.domain\">${login_host}</stringProp>\n              <stringProp name=\"HTTPSampler.port\">${port}</stringProp>\n              <stringProp name=\"HTTPSampler.protocol\">${protocol}</stringProp>\n              <stringProp name=\"HTTPSampler.contentEncoding\"></stringProp>\n              <stringProp name=\"HTTPSampler.path\">/login/index.php</stringProp>\n              <stringProp name=\"HTTPSampler.method\">POST</stringProp>\n              <boolProp name=\"HTTPSampler.follow_redirects\">true</boolProp>\n              <boolProp name=\"HTTPSampler.auto_redirects\">false</boolProp>\n              <boolProp name=\"HTTPSampler.use_keepalive\">true</boolProp>\n              <boolProp name=\"HTTPSampler.DO_MULTIPART_POST\">false</boolProp>\n              <boolProp name=\"HTTPSampler.image_parser\">true</boolProp>\n              <stringProp name=\"HTTPSampler.embedded_url_re\"></stringProp>\n              <stringProp name=\"HTTPSampler.implementation\">Java</stringProp>\n              <stringProp name=\"HTTPSampler.connect_timeout\">${default_connect_timeout}</stringProp>\n              <stringProp name=\"HTTPSampler.response_timeout\">${default_response_timeout}</stringProp>\n            </HTTPSamplerProxy>\n            <hashTree>\n              <ResponseAssertion guiclass=\"AssertionGui\" testclass=\"ResponseAssertion\" testname=\"Assertion - Logged in Screen\" enabled=\"true\">\n                <collectionProp name=\"Asserion.test_strings\">\n                  <stringProp name=\"-1544167836\">You are logged in as </stringProp>\n                </collectionProp>\n                <intProp name=\"Assertion.test_type\">2</intProp>\n                <stringProp name=\"Assertion.test_field\">Assertion.response_data</stringProp>\n                <boolProp name=\"Assertion.assume_success\">false</boolProp>\n                <stringProp name=\"Assertion.custom_message\"></stringProp>\n              </ResponseAssertion>\n              <hashTree/>\n              <XPathExtractor guiclass=\"XPathExtractorGui\" testclass=\"XPathExtractor\" testname=\"Get sesskey\" enabled=\"true\">\n                <stringProp name=\"XPathExtractor.default\"></stringProp>\n                <stringProp name=\"XPathExtractor.refname\">sesskey</stringProp>\n                <stringProp name=\"XPathExtractor.xpathQuery\">//*[@name=&apos;sesskey&apos;][1]/@value</stringProp>\n                <boolProp name=\"XPathExtractor.validate\">false</boolProp>\n                <boolProp name=\"XPathExtractor.tolerant\">true</boolProp>\n                <boolProp name=\"XPathExtractor.namespace\">false</boolProp>\n                <stringProp name=\"XPathExtractor.matchNumber\">-1</stringProp>\n              </XPathExtractor>\n              <hashTree/>\n              <ConstantTimer guiclass=\"ConstantTimerGui\" testclass=\"ConstantTimer\" testname=\"Constant Timer\" enabled=\"false\">\n                <stringProp name=\"ConstantTimer.delay\">1000</stringProp>\n              </ConstantTimer>\n              <hashTree/>\n              <GaussianRandomTimer guiclass=\"GaussianRandomTimerGui\" testclass=\"GaussianRandomTimer\" testname=\"Gaussian Random Timer\" enabled=\"true\">\n                <stringProp name=\"ConstantTimer.delay\">5000</stringProp>\n                <stringProp name=\"RandomTimer.range\">0</stringProp>\n              </GaussianRandomTimer>\n              <hashTree/>\n              <ResultCollector guiclass=\"ViewResultsFullVisualizer\" testclass=\"ResultCollector\" testname=\"View Results Tree\" enabled=\"false\">\n                <boolProp name=\"ResultCollector.error_logging\">false</boolProp>\n                <objProp>\n                  <name>saveConfig</name>\n                  <value class=\"SampleSaveConfiguration\">\n                    <time>true</time>\n                    <latency>true</latency>\n                    <timestamp>true</timestamp>\n                    <success>true</success>\n                    <label>true</label>\n                    <code>true</code>\n                    <message>true</message>\n                    <threadName>true</threadName>\n                    <dataType>true</dataType>\n                    <encoding>false</encoding>\n                    <assertions>true</assertions>\n                    <subresults>true</subresults>\n                    <responseData>false</responseData>\n                    <samplerData>false</samplerData>\n                    <xml>false</xml>\n                    <fieldNames>true</fieldNames>\n                    <responseHeaders>false</responseHeaders>\n                    <requestHeaders>false</requestHeaders>\n                    <responseDataOnError>false</responseDataOnError>\n                    <saveAssertionResultsFailureMessage>true</saveAssertionResultsFailureMessage>\n                    <assertionsResultsToSave>0</assertionsResultsToSave>\n                    <bytes>true</bytes>\n                    <sentBytes>true</sentBytes>\n                    <threadCounts>true</threadCounts>\n                    <idleTime>true</idleTime>\n                    <connectTime>true</connectTime>\n                  </value>\n                </objProp>\n                <stringProp name=\"filename\"></stringProp>\n              </ResultCollector>\n              <hashTree/>\n            </hashTree>\n            <LoopController guiclass=\"LoopControlPanel\" testclass=\"LoopController\" testname=\"Loop x5 - View one of my courses, and a resource\" enabled=\"true\">\n              <boolProp name=\"LoopController.continue_forever\">true</boolProp>\n              <stringProp name=\"LoopController.loops\">5</stringProp>\n            </LoopController>\n            <hashTree>\n              <UserParameters guiclass=\"UserParametersGui\" testclass=\"UserParameters\" testname=\"User Parameters\" enabled=\"true\">\n                <collectionProp name=\"UserParameters.names\">\n                  <stringProp name=\"-852875554\">RandomCourse</stringProp>\n                  <stringProp name=\"-368356746\">courseid</stringProp>\n                </collectionProp>\n                <collectionProp name=\"UserParameters.thread_values\">\n                  <collectionProp name=\"-883654058\">\n                    <stringProp name=\"1948420855\">${__Random(1,${course_id_n},ReuseRandomNumber)}</stringProp>\n                    <stringProp name=\"-1361716841\">${activity_course_id}</stringProp>\n                  </collectionProp>\n                </collectionProp>\n                <boolProp name=\"UserParameters.per_iteration\">true</boolProp>\n              </UserParameters>\n              <hashTree/>\n              <HTTPSamplerProxy guiclass=\"HttpTestSampleGui\" testclass=\"HTTPSamplerProxy\" testname=\"Go to course\" enabled=\"true\">\n                <elementProp name=\"HTTPsampler.Arguments\" elementType=\"Arguments\" guiclass=\"HTTPArgumentsPanel\" testclass=\"Arguments\" testname=\"Argument List\" enabled=\"true\">\n                  <collectionProp name=\"Arguments.arguments\">\n                    <elementProp name=\"id\" elementType=\"HTTPArgument\">\n                      <boolProp name=\"HTTPArgument.always_encode\">false</boolProp>\n                      <stringProp name=\"Argument.value\">${courseid}</stringProp>\n                      <stringProp name=\"Argument.name\">id</stringProp>\n                      <stringProp name=\"Argument.metadata\">=</stringProp>\n                      <stringProp name=\"Argument.use_equals\">true</stringProp>\n                      <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                    </elementProp>\n                  </collectionProp>\n                </elementProp>\n                <stringProp name=\"HTTPSampler.domain\">${host}</stringProp>\n                <stringProp name=\"HTTPSampler.port\">${port}</stringProp>\n                <stringProp name=\"HTTPSampler.protocol\">${protocol}</stringProp>\n                <stringProp name=\"HTTPSampler.contentEncoding\"></stringProp>\n                <stringProp name=\"HTTPSampler.path\">/course/view.php</stringProp>\n                <stringProp name=\"HTTPSampler.method\">GET</stringProp>\n                <boolProp name=\"HTTPSampler.follow_redirects\">true</boolProp>\n                <boolProp name=\"HTTPSampler.auto_redirects\">false</boolProp>\n                <boolProp name=\"HTTPSampler.use_keepalive\">true</boolProp>\n                <boolProp name=\"HTTPSampler.DO_MULTIPART_POST\">false</boolProp>\n                <stringProp name=\"HTTPSampler.embedded_url_re\"></stringProp>\n                <stringProp name=\"HTTPSampler.implementation\">Java</stringProp>\n                <stringProp name=\"HTTPSampler.connect_timeout\">${default_connect_timeout}</stringProp>\n                <stringProp name=\"HTTPSampler.response_timeout\">${default_response_timeout}</stringProp>\n              </HTTPSamplerProxy>\n              <hashTree>\n                <ConstantTimer guiclass=\"ConstantTimerGui\" testclass=\"ConstantTimer\" testname=\"Constant Timer\" enabled=\"false\">\n                  <stringProp name=\"ConstantTimer.delay\">1000</stringProp>\n                </ConstantTimer>\n                <hashTree/>\n                <GaussianRandomTimer guiclass=\"GaussianRandomTimerGui\" testclass=\"GaussianRandomTimer\" testname=\"Gaussian Random Timer\" enabled=\"true\">\n                  <stringProp name=\"ConstantTimer.delay\">10000</stringProp>\n                  <stringProp name=\"RandomTimer.range\">25000</stringProp>\n                </GaussianRandomTimer>\n                <hashTree/>\n                <RegexExtractor guiclass=\"RegexExtractorGui\" testclass=\"RegexExtractor\" testname=\"RegExp - Get random resource_module_id\" enabled=\"true\">\n                  <stringProp name=\"RegexExtractor.useHeaders\">false</stringProp>\n                  <stringProp name=\"RegexExtractor.refname\">resource_module_id</stringProp>\n                  <stringProp name=\"RegexExtractor.regex\">\\/mod\\/resource\\/view.php\\?id=(\\d+)&quot;</stringProp>\n                  <stringProp name=\"RegexExtractor.template\">$1$</stringProp>\n                  <stringProp name=\"RegexExtractor.default\">NULL</stringProp>\n                  <stringProp name=\"RegexExtractor.match_number\">0</stringProp>\n                </RegexExtractor>\n                <hashTree/>\n              </hashTree>\n              <IfController guiclass=\"IfControllerPanel\" testclass=\"IfController\" testname=\"Resource View (30% frequency the time)\" enabled=\"false\">\n                <stringProp name=\"IfController.condition\">${__Random(1,10,ReuseRandomNumber)} &lt;= 10</stringProp>\n                <boolProp name=\"IfController.evaluateAll\">false</boolProp>\n              </IfController>\n              <hashTree>\n                <IfController guiclass=\"IfControllerPanel\" testclass=\"IfController\" testname=\"View resource IF there is one\" enabled=\"true\">\n                  <stringProp name=\"IfController.condition\">&quot;${resource_module_id}&quot; != &quot;NULL&quot;</stringProp>\n                  <boolProp name=\"IfController.evaluateAll\">false</boolProp>\n                </IfController>\n                <hashTree>\n                  <HTTPSamplerProxy guiclass=\"HttpTestSampleGui\" testclass=\"HTTPSamplerProxy\" testname=\"Open the resource page\" enabled=\"true\">\n                    <elementProp name=\"HTTPsampler.Arguments\" elementType=\"Arguments\" guiclass=\"HTTPArgumentsPanel\" testclass=\"Arguments\" testname=\"Argument List\" enabled=\"true\">\n                      <collectionProp name=\"Arguments.arguments\">\n                        <elementProp name=\"id\" elementType=\"HTTPArgument\">\n                          <boolProp name=\"HTTPArgument.always_encode\">false</boolProp>\n                          <stringProp name=\"Argument.value\">${resource_module_id}</stringProp>\n                          <stringProp name=\"Argument.name\">id</stringProp>\n                          <stringProp name=\"Argument.metadata\">=</stringProp>\n                          <stringProp name=\"Argument.use_equals\">true</stringProp>\n                          <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                        </elementProp>\n                      </collectionProp>\n                    </elementProp>\n                    <stringProp name=\"HTTPSampler.domain\">${host}</stringProp>\n                    <stringProp name=\"HTTPSampler.port\">${port}</stringProp>\n                    <stringProp name=\"HTTPSampler.protocol\">${protocol}</stringProp>\n                    <stringProp name=\"HTTPSampler.contentEncoding\"></stringProp>\n                    <stringProp name=\"HTTPSampler.path\">/mod/resource/view.php</stringProp>\n                    <stringProp name=\"HTTPSampler.method\">GET</stringProp>\n                    <boolProp name=\"HTTPSampler.follow_redirects\">true</boolProp>\n                    <boolProp name=\"HTTPSampler.auto_redirects\">false</boolProp>\n                    <boolProp name=\"HTTPSampler.use_keepalive\">true</boolProp>\n                    <boolProp name=\"HTTPSampler.DO_MULTIPART_POST\">false</boolProp>\n                    <stringProp name=\"HTTPSampler.embedded_url_re\"></stringProp>\n                    <stringProp name=\"HTTPSampler.implementation\">Java</stringProp>\n                    <stringProp name=\"HTTPSampler.connect_timeout\"></stringProp>\n                    <stringProp name=\"HTTPSampler.response_timeout\"></stringProp>\n                  </HTTPSamplerProxy>\n                  <hashTree>\n                    <ConstantTimer guiclass=\"ConstantTimerGui\" testclass=\"ConstantTimer\" testname=\"Constant Timer\" enabled=\"false\">\n                      <stringProp name=\"ConstantTimer.delay\">1000</stringProp>\n                    </ConstantTimer>\n                    <hashTree/>\n                    <GaussianRandomTimer guiclass=\"GaussianRandomTimerGui\" testclass=\"GaussianRandomTimer\" testname=\"Gaussian Random Timer\" enabled=\"true\">\n                      <stringProp name=\"ConstantTimer.delay\">3000</stringProp>\n                      <stringProp name=\"RandomTimer.range\">5000</stringProp>\n                    </GaussianRandomTimer>\n                    <hashTree/>\n                  </hashTree>\n                </hashTree>\n              </hashTree>\n              <IfController guiclass=\"IfControllerPanel\" testclass=\"IfController\" testname=\"File upload test\" enabled=\"true\">\n                <stringProp name=\"IfController.condition\">${__Random(1,10,ReuseRandomNumber)} &lt;= 10</stringProp>\n                <boolProp name=\"IfController.evaluateAll\">false</boolProp>\n              </IfController>\n              <hashTree>\n                <HTTPSamplerProxy guiclass=\"HttpTestSampleGui\" testclass=\"HTTPSamplerProxy\" testname=\"Open the user files\" enabled=\"true\">\n                  <elementProp name=\"HTTPsampler.Arguments\" elementType=\"Arguments\" guiclass=\"HTTPArgumentsPanel\" testclass=\"Arguments\" testname=\"Argument List\" enabled=\"true\">\n                    <collectionProp name=\"Arguments.arguments\"/>\n                  </elementProp>\n                  <stringProp name=\"HTTPSampler.domain\">${host}</stringProp>\n                  <stringProp name=\"HTTPSampler.port\">${port}</stringProp>\n                  <stringProp name=\"HTTPSampler.protocol\">${protocol}</stringProp>\n                  <stringProp name=\"HTTPSampler.contentEncoding\"></stringProp>\n                  <stringProp name=\"HTTPSampler.path\">/user/files.php</stringProp>\n                  <stringProp name=\"HTTPSampler.method\">GET</stringProp>\n                  <boolProp name=\"HTTPSampler.follow_redirects\">true</boolProp>\n                  <boolProp name=\"HTTPSampler.auto_redirects\">false</boolProp>\n                  <boolProp name=\"HTTPSampler.use_keepalive\">true</boolProp>\n                  <boolProp name=\"HTTPSampler.DO_MULTIPART_POST\">false</boolProp>\n                  <stringProp name=\"HTTPSampler.embedded_url_re\"></stringProp>\n                  <stringProp name=\"HTTPSampler.implementation\">Java</stringProp>\n                  <stringProp name=\"HTTPSampler.connect_timeout\"></stringProp>\n                  <stringProp name=\"HTTPSampler.response_timeout\"></stringProp>\n                </HTTPSamplerProxy>\n                <hashTree>\n                  <RegexExtractor guiclass=\"RegexExtractorGui\" testclass=\"RegexExtractor\" testname=\"RegExp - Get sesskey\" enabled=\"true\">\n                    <stringProp name=\"RegexExtractor.useHeaders\">false</stringProp>\n                    <stringProp name=\"RegexExtractor.refname\">upload_sesskey</stringProp>\n                    <stringProp name=\"RegexExtractor.regex\">name=&quot;sesskey&quot;.*?value=&quot;([\\w]+)&quot;</stringProp>\n                    <stringProp name=\"RegexExtractor.template\">$1$</stringProp>\n                    <stringProp name=\"RegexExtractor.default\">NULL</stringProp>\n                    <stringProp name=\"RegexExtractor.match_number\">1</stringProp>\n                  </RegexExtractor>\n                  <hashTree/>\n                  <RegexExtractor guiclass=\"RegexExtractorGui\" testclass=\"RegexExtractor\" testname=\"RegExp - Get filemanager id (itemid)\" enabled=\"true\">\n                    <stringProp name=\"RegexExtractor.useHeaders\">false</stringProp>\n                    <stringProp name=\"RegexExtractor.refname\">upload_filemanager_id</stringProp>\n                    <stringProp name=\"RegexExtractor.regex\">&quot;itemid&quot;:([\\d]+)</stringProp>\n                    <stringProp name=\"RegexExtractor.template\">$1$</stringProp>\n                    <stringProp name=\"RegexExtractor.default\">NULL</stringProp>\n                    <stringProp name=\"RegexExtractor.match_number\">1</stringProp>\n                  </RegexExtractor>\n                  <hashTree/>\n                  <RegexExtractor guiclass=\"RegexExtractorGui\" testclass=\"RegexExtractor\" testname=\"RegExp - Get client_id\" enabled=\"true\">\n                    <stringProp name=\"RegexExtractor.useHeaders\">false</stringProp>\n                    <stringProp name=\"RegexExtractor.refname\">upload_clientid</stringProp>\n                    <stringProp name=\"RegexExtractor.regex\">&quot;client_id&quot;:&quot;([\\w]+)&quot;</stringProp>\n                    <stringProp name=\"RegexExtractor.template\">$1$</stringProp>\n                    <stringProp name=\"RegexExtractor.default\">NULL</stringProp>\n                    <stringProp name=\"RegexExtractor.match_number\">1</stringProp>\n                  </RegexExtractor>\n                  <hashTree/>\n                  <RegexExtractor guiclass=\"RegexExtractorGui\" testclass=\"RegexExtractor\" testname=\"RegExp - Get ctx id\" enabled=\"true\">\n                    <stringProp name=\"RegexExtractor.useHeaders\">false</stringProp>\n                    <stringProp name=\"RegexExtractor.refname\">upload_ctxid</stringProp>\n                    <stringProp name=\"RegexExtractor.regex\">ctx_id=([\\d]+)</stringProp>\n                    <stringProp name=\"RegexExtractor.template\">$1$</stringProp>\n                    <stringProp name=\"RegexExtractor.default\">NULL</stringProp>\n                    <stringProp name=\"RegexExtractor.match_number\">1</stringProp>\n                  </RegexExtractor>\n                  <hashTree/>\n                  <ConstantTimer guiclass=\"ConstantTimerGui\" testclass=\"ConstantTimer\" testname=\"Constant Timer\" enabled=\"false\">\n                    <stringProp name=\"ConstantTimer.delay\">1000</stringProp>\n                  </ConstantTimer>\n                  <hashTree/>\n                  <GaussianRandomTimer guiclass=\"GaussianRandomTimerGui\" testclass=\"GaussianRandomTimer\" testname=\"Gaussian Random Timer\" enabled=\"true\">\n                    <stringProp name=\"ConstantTimer.delay\">3000</stringProp>\n                    <stringProp name=\"RandomTimer.range\">5000</stringProp>\n                  </GaussianRandomTimer>\n                  <hashTree/>\n                </hashTree>\n                <HTTPSamplerProxy guiclass=\"HttpTestSampleGui\" testclass=\"HTTPSamplerProxy\" testname=\"Upload a file\" enabled=\"true\">\n                  <elementProp name=\"HTTPsampler.Files\" elementType=\"HTTPFileArgs\">\n                    <collectionProp name=\"HTTPFileArgs.files\">\n                      <elementProp name=\"${upload_test_filename}\" elementType=\"HTTPFileArg\">\n                        <stringProp name=\"File.path\">${upload_test_filename}</stringProp>\n                        <stringProp name=\"File.paramname\">repo_upload_file</stringProp>\n                        <stringProp name=\"File.mimetype\">application/octet-stream</stringProp>\n                      </elementProp>\n                    </collectionProp>\n                  </elementProp>\n                  <elementProp name=\"HTTPsampler.Arguments\" elementType=\"Arguments\" guiclass=\"HTTPArgumentsPanel\" testclass=\"Arguments\" testname=\"Argument List\" enabled=\"true\">\n                    <collectionProp name=\"Arguments.arguments\">\n                      <elementProp name=\"title\" elementType=\"HTTPArgument\">\n                        <boolProp name=\"HTTPArgument.always_encode\">false</boolProp>\n                        <stringProp name=\"Argument.value\"></stringProp>\n                        <stringProp name=\"Argument.metadata\">=</stringProp>\n                        <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                        <stringProp name=\"Argument.name\">title</stringProp>\n                      </elementProp>\n                      <elementProp name=\"author\" elementType=\"HTTPArgument\">\n                        <boolProp name=\"HTTPArgument.always_encode\">false</boolProp>\n                        <stringProp name=\"Argument.value\">${username}</stringProp>\n                        <stringProp name=\"Argument.metadata\">=</stringProp>\n                        <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                        <stringProp name=\"Argument.name\">author</stringProp>\n                      </elementProp>\n                      <elementProp name=\"license\" elementType=\"HTTPArgument\">\n                        <boolProp name=\"HTTPArgument.always_encode\">false</boolProp>\n                        <stringProp name=\"Argument.value\">allrightsreserved</stringProp>\n                        <stringProp name=\"Argument.metadata\">=</stringProp>\n                        <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                        <stringProp name=\"Argument.name\">license</stringProp>\n                      </elementProp>\n                      <elementProp name=\"itemid\" elementType=\"HTTPArgument\">\n                        <boolProp name=\"HTTPArgument.always_encode\">false</boolProp>\n                        <stringProp name=\"Argument.value\">${upload_filemanager_id}</stringProp>\n                        <stringProp name=\"Argument.metadata\">=</stringProp>\n                        <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                        <stringProp name=\"Argument.name\">itemid</stringProp>\n                      </elementProp>\n                      <elementProp name=\"repo_id\" elementType=\"HTTPArgument\">\n                        <boolProp name=\"HTTPArgument.always_encode\">false</boolProp>\n                        <stringProp name=\"Argument.value\">8</stringProp>\n                        <stringProp name=\"Argument.metadata\">=</stringProp>\n                        <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                        <stringProp name=\"Argument.name\">repo_id</stringProp>\n                      </elementProp>\n                      <elementProp name=\"p\" elementType=\"HTTPArgument\">\n                        <boolProp name=\"HTTPArgument.always_encode\">false</boolProp>\n                        <stringProp name=\"Argument.value\"></stringProp>\n                        <stringProp name=\"Argument.metadata\">=</stringProp>\n                        <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                        <stringProp name=\"Argument.name\">p</stringProp>\n                      </elementProp>\n                      <elementProp name=\"page\" elementType=\"HTTPArgument\">\n                        <boolProp name=\"HTTPArgument.always_encode\">false</boolProp>\n                        <stringProp name=\"Argument.value\"></stringProp>\n                        <stringProp name=\"Argument.metadata\">=</stringProp>\n                        <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                        <stringProp name=\"Argument.name\">page</stringProp>\n                      </elementProp>\n                      <elementProp name=\"env\" elementType=\"HTTPArgument\">\n                        <boolProp name=\"HTTPArgument.always_encode\">false</boolProp>\n                        <stringProp name=\"Argument.value\">filemanager</stringProp>\n                        <stringProp name=\"Argument.metadata\">=</stringProp>\n                        <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                        <stringProp name=\"Argument.name\">env</stringProp>\n                      </elementProp>\n                      <elementProp name=\"sesskey\" elementType=\"HTTPArgument\">\n                        <boolProp name=\"HTTPArgument.always_encode\">false</boolProp>\n                        <stringProp name=\"Argument.value\">${upload_sesskey}</stringProp>\n                        <stringProp name=\"Argument.metadata\">=</stringProp>\n                        <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                        <stringProp name=\"Argument.name\">sesskey</stringProp>\n                      </elementProp>\n                      <elementProp name=\"client_id\" elementType=\"HTTPArgument\">\n                        <boolProp name=\"HTTPArgument.always_encode\">false</boolProp>\n                        <stringProp name=\"Argument.value\">${upload_clientid}</stringProp>\n                        <stringProp name=\"Argument.metadata\">=</stringProp>\n                        <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                        <stringProp name=\"Argument.name\">client_id</stringProp>\n                      </elementProp>\n                      <elementProp name=\"itemid\" elementType=\"HTTPArgument\">\n                        <boolProp name=\"HTTPArgument.always_encode\">false</boolProp>\n                        <stringProp name=\"Argument.value\">${upload_filemanager_id}</stringProp>\n                        <stringProp name=\"Argument.metadata\">=</stringProp>\n                        <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                        <stringProp name=\"Argument.name\">itemid</stringProp>\n                      </elementProp>\n                      <elementProp name=\"maxbytes\" elementType=\"HTTPArgument\">\n                        <boolProp name=\"HTTPArgument.always_encode\">false</boolProp>\n                        <stringProp name=\"Argument.value\">-1</stringProp>\n                        <stringProp name=\"Argument.metadata\">=</stringProp>\n                        <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                        <stringProp name=\"Argument.name\">maxbytes</stringProp>\n                      </elementProp>\n                      <elementProp name=\"areamaxbytes\" elementType=\"HTTPArgument\">\n                        <boolProp name=\"HTTPArgument.always_encode\">false</boolProp>\n                        <stringProp name=\"Argument.value\">-1</stringProp>\n                        <stringProp name=\"Argument.metadata\">=</stringProp>\n                        <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                        <stringProp name=\"Argument.name\">areamaxbytes</stringProp>\n                      </elementProp>\n                      <elementProp name=\"ctx_id\" elementType=\"HTTPArgument\">\n                        <boolProp name=\"HTTPArgument.always_encode\">false</boolProp>\n                        <stringProp name=\"Argument.value\">${upload_ctxid}</stringProp>\n                        <stringProp name=\"Argument.metadata\">=</stringProp>\n                        <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                        <stringProp name=\"Argument.name\">ctx_id</stringProp>\n                      </elementProp>\n                      <elementProp name=\"savepath\" elementType=\"HTTPArgument\">\n                        <boolProp name=\"HTTPArgument.always_encode\">false</boolProp>\n                        <stringProp name=\"Argument.value\">/</stringProp>\n                        <stringProp name=\"Argument.metadata\">=</stringProp>\n                        <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                        <stringProp name=\"Argument.name\">savepath</stringProp>\n                      </elementProp>\n                    </collectionProp>\n                  </elementProp>\n                  <stringProp name=\"HTTPSampler.domain\">${host}</stringProp>\n                  <stringProp name=\"HTTPSampler.port\">${port}</stringProp>\n                  <stringProp name=\"HTTPSampler.protocol\">${protocol}</stringProp>\n                  <stringProp name=\"HTTPSampler.contentEncoding\"></stringProp>\n                  <stringProp name=\"HTTPSampler.path\">/repository/repository_ajax.php?action=upload</stringProp>\n                  <stringProp name=\"HTTPSampler.method\">POST</stringProp>\n                  <boolProp name=\"HTTPSampler.follow_redirects\">true</boolProp>\n                  <boolProp name=\"HTTPSampler.auto_redirects\">false</boolProp>\n                  <boolProp name=\"HTTPSampler.use_keepalive\">true</boolProp>\n                  <boolProp name=\"HTTPSampler.DO_MULTIPART_POST\">true</boolProp>\n                  <stringProp name=\"HTTPSampler.embedded_url_re\"></stringProp>\n                  <stringProp name=\"HTTPSampler.implementation\">Java</stringProp>\n                  <stringProp name=\"HTTPSampler.connect_timeout\"></stringProp>\n                  <stringProp name=\"HTTPSampler.response_timeout\"></stringProp>\n                </HTTPSamplerProxy>\n                <hashTree>\n                  <ConstantTimer guiclass=\"ConstantTimerGui\" testclass=\"ConstantTimer\" testname=\"Constant Timer\" enabled=\"false\">\n                    <stringProp name=\"ConstantTimer.delay\">1000</stringProp>\n                  </ConstantTimer>\n                  <hashTree/>\n                  <GaussianRandomTimer guiclass=\"GaussianRandomTimerGui\" testclass=\"GaussianRandomTimer\" testname=\"Gaussian Random Timer\" enabled=\"true\">\n                    <stringProp name=\"ConstantTimer.delay\">3000</stringProp>\n                    <stringProp name=\"RandomTimer.range\">5000</stringProp>\n                  </GaussianRandomTimer>\n                  <hashTree/>\n                  <BeanShellPreProcessor guiclass=\"TestBeanGUI\" testclass=\"BeanShellPreProcessor\" testname=\"BeanShell PreProcessor\" enabled=\"true\">\n                    <boolProp name=\"resetInterpreter\">false</boolProp>\n                    <stringProp name=\"parameters\"></stringProp>\n                    <stringProp name=\"filename\"></stringProp>\n                    <stringProp name=\"script\">import org.apache.commons.io.FileUtils;\nimport org.apache.commons.lang3.RandomStringUtils;\nimport org.apache.commons.lang3.RandomUtils;\n\n// Create a temp file\nFile myFile = File.createTempFile(&quot;upload-&quot;, &quot;&quot; );\n\n// Generate Random length string and write to file\nFileUtils.writeStringToFile(myFile, RandomStringUtils.random( RandomUtils.nextInt( 500000, 5000000 ) ), &quot;UTF-8&quot; );\n\n// Store file name in variable.\nvars.put( &quot;upload_test_filename&quot;, myFile.getCanonicalPath() );</stringProp>\n                  </BeanShellPreProcessor>\n                  <hashTree/>\n                  <BeanShellPostProcessor guiclass=\"TestBeanGUI\" testclass=\"BeanShellPostProcessor\" testname=\"BeanShell PostProcessor\" enabled=\"true\">\n                    <stringProp name=\"filename\"></stringProp>\n                    <stringProp name=\"parameters\"></stringProp>\n                    <boolProp name=\"resetInterpreter\">false</boolProp>\n                    <stringProp name=\"script\">import org.apache.commons.io.FileUtils;\n// Delete file and do not throw error\nFileUtils.deleteQuietly(new File( vars.get(&quot;upload_test_filename&quot;)));</stringProp>\n                  </BeanShellPostProcessor>\n                  <hashTree/>\n                </hashTree>\n                <HTTPSamplerProxy guiclass=\"HttpTestSampleGui\" testclass=\"HTTPSamplerProxy\" testname=\"Save changes\" enabled=\"true\">\n                  <elementProp name=\"HTTPsampler.Arguments\" elementType=\"Arguments\" guiclass=\"HTTPArgumentsPanel\" testclass=\"Arguments\" testname=\"Argument List\" enabled=\"true\">\n                    <collectionProp name=\"Arguments.arguments\">\n                      <elementProp name=\"returnurl\" elementType=\"HTTPArgument\">\n                        <boolProp name=\"HTTPArgument.always_encode\">true</boolProp>\n                        <stringProp name=\"Argument.value\">${protocol}://${host}/user/files.php</stringProp>\n                        <stringProp name=\"Argument.metadata\">=</stringProp>\n                        <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                        <stringProp name=\"Argument.name\">returnurl</stringProp>\n                      </elementProp>\n                      <elementProp name=\"sesskey\" elementType=\"HTTPArgument\">\n                        <boolProp name=\"HTTPArgument.always_encode\">false</boolProp>\n                        <stringProp name=\"Argument.value\">${upload_sesskey}</stringProp>\n                        <stringProp name=\"Argument.metadata\">=</stringProp>\n                        <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                        <stringProp name=\"Argument.name\">sesskey</stringProp>\n                      </elementProp>\n                      <elementProp name=\"_qf__user_files_form\" elementType=\"HTTPArgument\">\n                        <boolProp name=\"HTTPArgument.always_encode\">false</boolProp>\n                        <stringProp name=\"Argument.value\">1</stringProp>\n                        <stringProp name=\"Argument.metadata\">=</stringProp>\n                        <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                        <stringProp name=\"Argument.name\">_qf__user_files_form</stringProp>\n                      </elementProp>\n                      <elementProp name=\"files_filemanager\" elementType=\"HTTPArgument\">\n                        <boolProp name=\"HTTPArgument.always_encode\">false</boolProp>\n                        <stringProp name=\"Argument.value\">${upload_filemanager_id}</stringProp>\n                        <stringProp name=\"Argument.metadata\">=</stringProp>\n                        <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                        <stringProp name=\"Argument.name\">files_filemanager</stringProp>\n                      </elementProp>\n                      <elementProp name=\"submitbutton\" elementType=\"HTTPArgument\">\n                        <boolProp name=\"HTTPArgument.always_encode\">false</boolProp>\n                        <stringProp name=\"Argument.value\">Save changes</stringProp>\n                        <stringProp name=\"Argument.metadata\">=</stringProp>\n                        <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                        <stringProp name=\"Argument.name\">submitbutton</stringProp>\n                      </elementProp>\n                    </collectionProp>\n                  </elementProp>\n                  <stringProp name=\"HTTPSampler.domain\">${host}</stringProp>\n                  <stringProp name=\"HTTPSampler.port\">${port}</stringProp>\n                  <stringProp name=\"HTTPSampler.protocol\">${protocol}</stringProp>\n                  <stringProp name=\"HTTPSampler.contentEncoding\"></stringProp>\n                  <stringProp name=\"HTTPSampler.path\">/user/files.php</stringProp>\n                  <stringProp name=\"HTTPSampler.method\">POST</stringProp>\n                  <boolProp name=\"HTTPSampler.follow_redirects\">true</boolProp>\n                  <boolProp name=\"HTTPSampler.auto_redirects\">false</boolProp>\n                  <boolProp name=\"HTTPSampler.use_keepalive\">true</boolProp>\n                  <boolProp name=\"HTTPSampler.DO_MULTIPART_POST\">false</boolProp>\n                  <stringProp name=\"HTTPSampler.embedded_url_re\"></stringProp>\n                  <stringProp name=\"HTTPSampler.implementation\">Java</stringProp>\n                  <stringProp name=\"HTTPSampler.connect_timeout\"></stringProp>\n                  <stringProp name=\"HTTPSampler.response_timeout\"></stringProp>\n                </HTTPSamplerProxy>\n                <hashTree>\n                  <ConstantTimer guiclass=\"ConstantTimerGui\" testclass=\"ConstantTimer\" testname=\"Constant Timer\" enabled=\"false\">\n                    <stringProp name=\"ConstantTimer.delay\">1000</stringProp>\n                  </ConstantTimer>\n                  <hashTree/>\n                  <GaussianRandomTimer guiclass=\"GaussianRandomTimerGui\" testclass=\"GaussianRandomTimer\" testname=\"Gaussian Random Timer\" enabled=\"true\">\n                    <stringProp name=\"ConstantTimer.delay\">3000</stringProp>\n                    <stringProp name=\"RandomTimer.range\">5000</stringProp>\n                  </GaussianRandomTimer>\n                  <hashTree/>\n                </hashTree>\n              </hashTree>\n              <DebugSampler guiclass=\"TestBeanGUI\" testclass=\"DebugSampler\" testname=\"Debug Sampler\" enabled=\"false\">\n                <boolProp name=\"displayJMeterProperties\">false</boolProp>\n                <boolProp name=\"displayJMeterVariables\">true</boolProp>\n                <boolProp name=\"displaySystemProperties\">false</boolProp>\n              </DebugSampler>\n              <hashTree/>\n              <ResultCollector guiclass=\"ViewResultsFullVisualizer\" testclass=\"ResultCollector\" testname=\"View Results Tree\" enabled=\"true\">\n                <boolProp name=\"ResultCollector.error_logging\">false</boolProp>\n                <objProp>\n                  <name>saveConfig</name>\n                  <value class=\"SampleSaveConfiguration\">\n                    <time>true</time>\n                    <latency>true</latency>\n                    <timestamp>true</timestamp>\n                    <success>true</success>\n                    <label>true</label>\n                    <code>true</code>\n                    <message>true</message>\n                    <threadName>true</threadName>\n                    <dataType>true</dataType>\n                    <encoding>false</encoding>\n                    <assertions>true</assertions>\n                    <subresults>true</subresults>\n                    <responseData>false</responseData>\n                    <samplerData>false</samplerData>\n                    <xml>false</xml>\n                    <fieldNames>false</fieldNames>\n                    <responseHeaders>false</responseHeaders>\n                    <requestHeaders>false</requestHeaders>\n                    <responseDataOnError>false</responseDataOnError>\n                    <saveAssertionResultsFailureMessage>false</saveAssertionResultsFailureMessage>\n                    <assertionsResultsToSave>0</assertionsResultsToSave>\n                    <bytes>true</bytes>\n                    <threadCounts>true</threadCounts>\n                  </value>\n                </objProp>\n                <stringProp name=\"filename\"></stringProp>\n              </ResultCollector>\n              <hashTree/>\n            </hashTree>\n            <LoopController guiclass=\"LoopControlPanel\" testclass=\"LoopController\" testname=\"Loop x3\" enabled=\"false\">\n              <boolProp name=\"LoopController.continue_forever\">true</boolProp>\n              <stringProp name=\"LoopController.loops\">3</stringProp>\n            </LoopController>\n            <hashTree>\n              <IfController guiclass=\"IfControllerPanel\" testclass=\"IfController\" testname=\"View forum (20% of the time)\" enabled=\"true\">\n                <stringProp name=\"TestPlan.comments\">20% of the time</stringProp>\n                <stringProp name=\"IfController.condition\">${__Random(1,10,ReuseRandomNumber)} &lt;= 10</stringProp>\n                <boolProp name=\"IfController.evaluateAll\">false</boolProp>\n              </IfController>\n              <hashTree>\n                <HTTPSamplerProxy guiclass=\"HttpTestSampleGui\" testclass=\"HTTPSamplerProxy\" testname=\"Goto course\" enabled=\"true\">\n                  <elementProp name=\"HTTPsampler.Arguments\" elementType=\"Arguments\" guiclass=\"HTTPArgumentsPanel\" testclass=\"Arguments\" testname=\"Argument List\" enabled=\"true\">\n                    <collectionProp name=\"Arguments.arguments\">\n                      <elementProp name=\"id\" elementType=\"HTTPArgument\">\n                        <boolProp name=\"HTTPArgument.always_encode\">false</boolProp>\n                        <stringProp name=\"Argument.value\">${activity_course_id}</stringProp>\n                        <stringProp name=\"Argument.name\">id</stringProp>\n                        <stringProp name=\"Argument.metadata\">=</stringProp>\n                        <stringProp name=\"Argument.use_equals\">true</stringProp>\n                        <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                      </elementProp>\n                    </collectionProp>\n                  </elementProp>\n                  <stringProp name=\"HTTPSampler.domain\">${host}</stringProp>\n                  <stringProp name=\"HTTPSampler.port\">${port}</stringProp>\n                  <stringProp name=\"HTTPSampler.protocol\">${protocol}</stringProp>\n                  <stringProp name=\"HTTPSampler.contentEncoding\"></stringProp>\n                  <stringProp name=\"HTTPSampler.path\">/course/view.php</stringProp>\n                  <stringProp name=\"HTTPSampler.method\">GET</stringProp>\n                  <boolProp name=\"HTTPSampler.follow_redirects\">true</boolProp>\n                  <boolProp name=\"HTTPSampler.auto_redirects\">false</boolProp>\n                  <boolProp name=\"HTTPSampler.use_keepalive\">true</boolProp>\n                  <boolProp name=\"HTTPSampler.DO_MULTIPART_POST\">false</boolProp>\n                  <stringProp name=\"HTTPSampler.embedded_url_re\"></stringProp>\n                  <stringProp name=\"HTTPSampler.implementation\">Java</stringProp>\n                  <stringProp name=\"HTTPSampler.connect_timeout\"></stringProp>\n                  <stringProp name=\"HTTPSampler.response_timeout\"></stringProp>\n                </HTTPSamplerProxy>\n                <hashTree>\n                  <ResponseAssertion guiclass=\"AssertionGui\" testclass=\"ResponseAssertion\" testname=\"Assertion - in the course?\" enabled=\"true\">\n                    <collectionProp name=\"Asserion.test_strings\">\n                      <stringProp name=\"1915803329\">Performance Testing Forum</stringProp>\n                    </collectionProp>\n                    <intProp name=\"Assertion.test_type\">2</intProp>\n                    <stringProp name=\"Assertion.test_field\">Assertion.response_data</stringProp>\n                    <boolProp name=\"Assertion.assume_success\">false</boolProp>\n                    <stringProp name=\"Assertion.custom_message\"></stringProp>\n                  </ResponseAssertion>\n                  <hashTree/>\n                  <RegexExtractor guiclass=\"RegexExtractorGui\" testclass=\"RegexExtractor\" testname=\"RegExp Extractor - Get coursetopics_html\" enabled=\"true\">\n                    <stringProp name=\"RegexExtractor.useHeaders\">false</stringProp>\n                    <stringProp name=\"RegexExtractor.refname\">coursetopics_html</stringProp>\n                    <stringProp name=\"RegexExtractor.regex\">Performance Testing Peak Quiz.*(&lt;div&gt;&lt;div class=&quot;mod-indent-outer&quot;&gt;.*?&lt;a .*Performance Testing Forum 0.*Performance Testing Forum 9.*?&lt;\\/a&gt;)</stringProp>\n                    <stringProp name=\"RegexExtractor.template\">$1$</stringProp>\n                    <stringProp name=\"RegexExtractor.default\">NULL</stringProp>\n                    <stringProp name=\"RegexExtractor.match_number\"></stringProp>\n                  </RegexExtractor>\n                  <hashTree/>\n                  <XPathExtractor guiclass=\"XPathExtractorGui\" testclass=\"XPathExtractor\" testname=\"XPath Extractor - Get Random Performance Forum Module id\" enabled=\"true\">\n                    <stringProp name=\"XPathExtractor.default\">NULL</stringProp>\n                    <stringProp name=\"XPathExtractor.refname\">moduleid</stringProp>\n                    <stringProp name=\"XPathExtractor.xpathQuery\">substring-after(//*[contains(text(),&apos;Performance Testing Forum ${__Random(0,9,ReuseRandomNumber)}&apos;)]/../@href,&apos;id=&apos;)</stringProp>\n                    <boolProp name=\"XPathExtractor.validate\">false</boolProp>\n                    <boolProp name=\"XPathExtractor.tolerant\">true</boolProp>\n                    <boolProp name=\"XPathExtractor.namespace\">false</boolProp>\n                    <boolProp name=\"XPathExtractor.show_warnings\">true</boolProp>\n                    <boolProp name=\"XPathExtractor.quiet\">false</boolProp>\n                    <stringProp name=\"TestPlan.comments\">Pick one of the 10 Random forums, we use 10 to reduce the index impact of one becoming large and exploding the query time to view the forum</stringProp>\n                    <boolProp name=\"XPathExtractor.report_errors\">true</boolProp>\n                    <stringProp name=\"Sample.scope\">variable</stringProp>\n                    <stringProp name=\"Scope.variable\">coursetopics_html</stringProp>\n                    <stringProp name=\"XPathExtractor.matchNumber\">-1</stringProp>\n                  </XPathExtractor>\n                  <hashTree/>\n                  <XPathExtractor guiclass=\"XPathExtractorGui\" testclass=\"XPathExtractor\" testname=\"XPath Extractor - Get module id\" enabled=\"false\">\n                    <stringProp name=\"XPathExtractor.default\">NULL</stringProp>\n                    <stringProp name=\"XPathExtractor.refname\">moduleid</stringProp>\n                    <stringProp name=\"XPathExtractor.xpathQuery\">substring-after(//*/div[@class=&quot;activityinstance&quot;]/*/*[contains(text(),&apos;Performance Testing Forum ${__Random(0,9,ReuseRandomNumber)}&apos;)]/../@href,&apos;id=&apos;)</stringProp>\n                    <boolProp name=\"XPathExtractor.validate\">false</boolProp>\n                    <boolProp name=\"XPathExtractor.tolerant\">true</boolProp>\n                    <boolProp name=\"XPathExtractor.namespace\">false</boolProp>\n                    <boolProp name=\"XPathExtractor.show_warnings\">true</boolProp>\n                    <boolProp name=\"XPathExtractor.quiet\">false</boolProp>\n                    <stringProp name=\"TestPlan.comments\">Pick one of the 10 Random forums, we use 10 to reduce the index impact of one becoming large and exploding the query time to view the forum</stringProp>\n                    <boolProp name=\"XPathExtractor.report_errors\">true</boolProp>\n                    <stringProp name=\"XPathExtractor.matchNumber\">-1</stringProp>\n                  </XPathExtractor>\n                  <hashTree/>\n                  <ConstantTimer guiclass=\"ConstantTimerGui\" testclass=\"ConstantTimer\" testname=\"Constant Timer\" enabled=\"false\">\n                    <stringProp name=\"ConstantTimer.delay\">1000</stringProp>\n                  </ConstantTimer>\n                  <hashTree/>\n                  <GaussianRandomTimer guiclass=\"GaussianRandomTimerGui\" testclass=\"GaussianRandomTimer\" testname=\"Gaussian Random Timer\" enabled=\"true\">\n                    <stringProp name=\"ConstantTimer.delay\">10000</stringProp>\n                    <stringProp name=\"RandomTimer.range\">30000</stringProp>\n                  </GaussianRandomTimer>\n                  <hashTree/>\n                </hashTree>\n                <HTTPSamplerProxy guiclass=\"HttpTestSampleGui\" testclass=\"HTTPSamplerProxy\" testname=\"Open Forum Module\" enabled=\"true\">\n                  <elementProp name=\"HTTPsampler.Arguments\" elementType=\"Arguments\" guiclass=\"HTTPArgumentsPanel\" testclass=\"Arguments\" testname=\"Argument List\" enabled=\"true\">\n                    <collectionProp name=\"Arguments.arguments\">\n                      <elementProp name=\"id\" elementType=\"HTTPArgument\">\n                        <boolProp name=\"HTTPArgument.always_encode\">false</boolProp>\n                        <stringProp name=\"Argument.value\">${moduleid}</stringProp>\n                        <stringProp name=\"Argument.name\">id</stringProp>\n                        <stringProp name=\"Argument.metadata\">=</stringProp>\n                        <stringProp name=\"Argument.use_equals\">true</stringProp>\n                        <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                      </elementProp>\n                    </collectionProp>\n                  </elementProp>\n                  <stringProp name=\"HTTPSampler.domain\">${host}</stringProp>\n                  <stringProp name=\"HTTPSampler.port\">${port}</stringProp>\n                  <stringProp name=\"HTTPSampler.protocol\">${protocol}</stringProp>\n                  <stringProp name=\"HTTPSampler.contentEncoding\"></stringProp>\n                  <stringProp name=\"HTTPSampler.path\">/mod/forum/view.php</stringProp>\n                  <stringProp name=\"HTTPSampler.method\">GET</stringProp>\n                  <boolProp name=\"HTTPSampler.follow_redirects\">true</boolProp>\n                  <boolProp name=\"HTTPSampler.auto_redirects\">false</boolProp>\n                  <boolProp name=\"HTTPSampler.use_keepalive\">true</boolProp>\n                  <boolProp name=\"HTTPSampler.DO_MULTIPART_POST\">false</boolProp>\n                  <stringProp name=\"HTTPSampler.embedded_url_re\"></stringProp>\n                  <stringProp name=\"HTTPSampler.implementation\">Java</stringProp>\n                  <stringProp name=\"HTTPSampler.connect_timeout\"></stringProp>\n                  <stringProp name=\"HTTPSampler.response_timeout\"></stringProp>\n                </HTTPSamplerProxy>\n                <hashTree>\n                  <ResponseAssertion guiclass=\"AssertionGui\" testclass=\"ResponseAssertion\" testname=\"Assertion - in the forum module ?\" enabled=\"true\">\n                    <collectionProp name=\"Asserion.test_strings\">\n                      <stringProp name=\"1915803329\">Performance Testing Forum</stringProp>\n                      <stringProp name=\"544764565\">Add a new discussion topic</stringProp>\n                    </collectionProp>\n                    <stringProp name=\"Assertion.test_field\">Assertion.response_data</stringProp>\n                    <boolProp name=\"Assertion.assume_success\">false</boolProp>\n                    <intProp name=\"Assertion.test_type\">2</intProp>\n                    <stringProp name=\"Assertion.custom_message\"></stringProp>\n                  </ResponseAssertion>\n                  <hashTree/>\n                  <RegexExtractor guiclass=\"RegexExtractorGui\" testclass=\"RegexExtractor\" testname=\"RegExp Extractor - Get forum discussion headings (if any!)\" enabled=\"true\">\n                    <stringProp name=\"RegexExtractor.useHeaders\">false</stringProp>\n                    <stringProp name=\"RegexExtractor.refname\">forumheaderlist</stringProp>\n                    <stringProp name=\"RegexExtractor.regex\">(?s)(&lt;table .*?class=&quot;forumheaderlist&quot;.*?&lt;\\/table&gt;)</stringProp>\n                    <stringProp name=\"RegexExtractor.template\">$1$</stringProp>\n                    <stringProp name=\"RegexExtractor.default\">NULL</stringProp>\n                    <stringProp name=\"RegexExtractor.match_number\"></stringProp>\n                  </RegexExtractor>\n                  <hashTree/>\n                  <XPathExtractor guiclass=\"XPathExtractorGui\" testclass=\"XPathExtractor\" testname=\"XPath Extractor - Get random discussion id (if any)\" enabled=\"true\">\n                    <stringProp name=\"XPathExtractor.default\">NULL</stringProp>\n                    <stringProp name=\"XPathExtractor.refname\">discuss_id</stringProp>\n                    <stringProp name=\"XPathExtractor.xpathQuery\">substring-after(//*/div[@class=&quot;activityinstance&quot;]/*/*[contains(text(),&apos;Performance Testing Forum ${__Random(0,9,ReuseRandomNumber)}&apos;)]/../@href,&apos;id=&apos;)</stringProp>\n                    <boolProp name=\"XPathExtractor.validate\">false</boolProp>\n                    <boolProp name=\"XPathExtractor.tolerant\">true</boolProp>\n                    <boolProp name=\"XPathExtractor.namespace\">false</boolProp>\n                    <boolProp name=\"XPathExtractor.show_warnings\">true</boolProp>\n                    <boolProp name=\"XPathExtractor.quiet\">false</boolProp>\n                    <stringProp name=\"TestPlan.comments\">Pick one of the 10 Random forums, we use 10 to reduce the index impact of one becoming large and exploding the query time to view the forum</stringProp>\n                    <boolProp name=\"XPathExtractor.report_errors\">true</boolProp>\n                    <stringProp name=\"Sample.scope\">variable</stringProp>\n                    <stringProp name=\"Scope.variable\">forumheaderlist</stringProp>\n                    <stringProp name=\"XPathExtractor.matchNumber\">-1</stringProp>\n                  </XPathExtractor>\n                  <hashTree/>\n                  <RegexExtractor guiclass=\"RegexExtractorGui\" testclass=\"RegexExtractor\" testname=\"RegExp Extractor - Get Forum id\" enabled=\"true\">\n                    <stringProp name=\"RegexExtractor.useHeaders\">false</stringProp>\n                    <stringProp name=\"RegexExtractor.refname\">forumid</stringProp>\n                    <stringProp name=\"RegexExtractor.regex\">name=&quot;forum&quot; value=&quot;(\\d+)&quot;</stringProp>\n                    <stringProp name=\"RegexExtractor.template\">$1$</stringProp>\n                    <stringProp name=\"RegexExtractor.default\">NULL</stringProp>\n                    <stringProp name=\"RegexExtractor.match_number\"></stringProp>\n                  </RegexExtractor>\n                  <hashTree/>\n                  <XPathExtractor guiclass=\"XPathExtractorGui\" testclass=\"XPathExtractor\" testname=\"Get forum id\" enabled=\"false\">\n                    <stringProp name=\"XPathExtractor.default\"></stringProp>\n                    <stringProp name=\"XPathExtractor.refname\">forumid</stringProp>\n                    <stringProp name=\"XPathExtractor.xpathQuery\">//*[@name=&apos;forum&apos;][1]/@value</stringProp>\n                    <boolProp name=\"XPathExtractor.validate\">false</boolProp>\n                    <boolProp name=\"XPathExtractor.tolerant\">true</boolProp>\n                    <boolProp name=\"XPathExtractor.namespace\">false</boolProp>\n                    <boolProp name=\"XPathExtractor.show_warnings\">true</boolProp>\n                    <boolProp name=\"XPathExtractor.report_errors\">true</boolProp>\n                    <boolProp name=\"XPathExtractor.quiet\">false</boolProp>\n                    <stringProp name=\"XPathExtractor.matchNumber\">-1</stringProp>\n                  </XPathExtractor>\n                  <hashTree/>\n                  <RegexExtractor guiclass=\"RegexExtractorGui\" testclass=\"RegexExtractor\" testname=\"RegExp Extractor - Get random discussion id\" enabled=\"true\">\n                    <stringProp name=\"RegexExtractor.useHeaders\">false</stringProp>\n                    <stringProp name=\"RegexExtractor.refname\">discuss_id</stringProp>\n                    <stringProp name=\"RegexExtractor.regex\">\\/mod\\/forum\\/discuss.php\\?d=(\\d+)</stringProp>\n                    <stringProp name=\"RegexExtractor.template\">$1$</stringProp>\n                    <stringProp name=\"RegexExtractor.default\">NULL</stringProp>\n                    <stringProp name=\"RegexExtractor.match_number\">0</stringProp>\n                  </RegexExtractor>\n                  <hashTree/>\n                  <RegexExtractor guiclass=\"RegexExtractorGui\" testclass=\"RegexExtractor\" testname=\"RegExp Extractor - Get corresponding forum discussion title\" enabled=\"true\">\n                    <stringProp name=\"RegexExtractor.useHeaders\">false</stringProp>\n                    <stringProp name=\"RegexExtractor.refname\">discuss_title</stringProp>\n                    <stringProp name=\"RegexExtractor.regex\">\\/mod\\/forum\\/discuss.php\\?d=${discuss_id}&quot;&gt;(.*?)&lt;/a&gt;</stringProp>\n                    <stringProp name=\"RegexExtractor.template\">$1$</stringProp>\n                    <stringProp name=\"RegexExtractor.default\">NULL</stringProp>\n                    <stringProp name=\"RegexExtractor.match_number\">1</stringProp>\n                  </RegexExtractor>\n                  <hashTree/>\n                  <ConstantTimer guiclass=\"ConstantTimerGui\" testclass=\"ConstantTimer\" testname=\"Constant Timer\" enabled=\"false\">\n                    <stringProp name=\"ConstantTimer.delay\">1000</stringProp>\n                  </ConstantTimer>\n                  <hashTree/>\n                  <GaussianRandomTimer guiclass=\"GaussianRandomTimerGui\" testclass=\"GaussianRandomTimer\" testname=\"Gaussian Random Timer\" enabled=\"true\">\n                    <stringProp name=\"ConstantTimer.delay\">25000</stringProp>\n                    <stringProp name=\"RandomTimer.range\">20000</stringProp>\n                  </GaussianRandomTimer>\n                  <hashTree/>\n                </hashTree>\n                <DebugSampler guiclass=\"TestBeanGUI\" testclass=\"DebugSampler\" testname=\"Debug Sampler\" enabled=\"false\">\n                  <boolProp name=\"displayJMeterProperties\">false</boolProp>\n                  <boolProp name=\"displayJMeterVariables\">true</boolProp>\n                  <boolProp name=\"displaySystemProperties\">false</boolProp>\n                </DebugSampler>\n                <hashTree/>\n                <ResultCollector guiclass=\"ViewResultsFullVisualizer\" testclass=\"ResultCollector\" testname=\"View Results Tree\" enabled=\"false\">\n                  <boolProp name=\"ResultCollector.error_logging\">false</boolProp>\n                  <objProp>\n                    <name>saveConfig</name>\n                    <value class=\"SampleSaveConfiguration\">\n                      <time>true</time>\n                      <latency>true</latency>\n                      <timestamp>true</timestamp>\n                      <success>true</success>\n                      <label>true</label>\n                      <code>true</code>\n                      <message>true</message>\n                      <threadName>true</threadName>\n                      <dataType>true</dataType>\n                      <encoding>false</encoding>\n                      <assertions>true</assertions>\n                      <subresults>true</subresults>\n                      <responseData>false</responseData>\n                      <samplerData>false</samplerData>\n                      <xml>false</xml>\n                      <fieldNames>false</fieldNames>\n                      <responseHeaders>false</responseHeaders>\n                      <requestHeaders>false</requestHeaders>\n                      <responseDataOnError>false</responseDataOnError>\n                      <saveAssertionResultsFailureMessage>false</saveAssertionResultsFailureMessage>\n                      <assertionsResultsToSave>0</assertionsResultsToSave>\n                      <bytes>true</bytes>\n                      <threadCounts>true</threadCounts>\n                    </value>\n                  </objProp>\n                  <stringProp name=\"filename\"></stringProp>\n                </ResultCollector>\n                <hashTree/>\n                <IfController guiclass=\"IfControllerPanel\" testclass=\"IfController\" testname=\"If a Forum Discussion Exists...\" enabled=\"true\">\n                  <stringProp name=\"IfController.condition\">&quot;${discuss_id}&quot; != &quot;NULL&quot; &amp;&amp; ${discuss_id} &gt; 0</stringProp>\n                  <boolProp name=\"IfController.evaluateAll\">false</boolProp>\n                </IfController>\n                <hashTree>\n                  <IfController guiclass=\"IfControllerPanel\" testclass=\"IfController\" testname=\"Forum View Discussion (50% of the time)\" enabled=\"true\">\n                    <stringProp name=\"IfController.condition\">${__Random(1,10,ReuseRandomNumber)} &lt;= 10</stringProp>\n                    <boolProp name=\"IfController.evaluateAll\">false</boolProp>\n                  </IfController>\n                  <hashTree>\n                    <HTTPSamplerProxy guiclass=\"HttpTestSampleGui\" testclass=\"HTTPSamplerProxy\" testname=\"Forum View Discussion\" enabled=\"true\">\n                      <elementProp name=\"HTTPsampler.Arguments\" elementType=\"Arguments\" guiclass=\"HTTPArgumentsPanel\" testclass=\"Arguments\" testname=\"Argument List\" enabled=\"true\">\n                        <collectionProp name=\"Arguments.arguments\">\n                          <elementProp name=\"d\" elementType=\"HTTPArgument\">\n                            <boolProp name=\"HTTPArgument.always_encode\">false</boolProp>\n                            <stringProp name=\"Argument.value\">${discuss_id}</stringProp>\n                            <stringProp name=\"Argument.name\">d</stringProp>\n                            <stringProp name=\"Argument.metadata\">=</stringProp>\n                            <stringProp name=\"Argument.use_equals\">true</stringProp>\n                            <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                          </elementProp>\n                        </collectionProp>\n                      </elementProp>\n                      <stringProp name=\"HTTPSampler.domain\">${host}</stringProp>\n                      <stringProp name=\"HTTPSampler.port\">${port}</stringProp>\n                      <stringProp name=\"HTTPSampler.protocol\">${protocol}</stringProp>\n                      <stringProp name=\"HTTPSampler.contentEncoding\"></stringProp>\n                      <stringProp name=\"HTTPSampler.path\">/mod/forum/discuss.php</stringProp>\n                      <stringProp name=\"HTTPSampler.method\">GET</stringProp>\n                      <boolProp name=\"HTTPSampler.follow_redirects\">true</boolProp>\n                      <boolProp name=\"HTTPSampler.auto_redirects\">false</boolProp>\n                      <boolProp name=\"HTTPSampler.use_keepalive\">true</boolProp>\n                      <boolProp name=\"HTTPSampler.DO_MULTIPART_POST\">false</boolProp>\n                      <stringProp name=\"HTTPSampler.embedded_url_re\"></stringProp>\n                      <stringProp name=\"HTTPSampler.implementation\">Java</stringProp>\n                      <stringProp name=\"HTTPSampler.connect_timeout\"></stringProp>\n                      <stringProp name=\"HTTPSampler.response_timeout\"></stringProp>\n                      <stringProp name=\"TestPlan.comments\">View a forum discussion thread</stringProp>\n                    </HTTPSamplerProxy>\n                    <hashTree>\n                      <ResponseAssertion guiclass=\"AssertionGui\" testclass=\"ResponseAssertion\" testname=\"Assertion - in the discussion thread ?\" enabled=\"true\">\n                        <collectionProp name=\"Asserion.test_strings\">\n                          <stringProp name=\"1415837435\">${discuss_title}</stringProp>\n                          <stringProp name=\"-1185574437\">My Course list is</stringProp>\n                        </collectionProp>\n                        <stringProp name=\"Assertion.test_field\">Assertion.response_data</stringProp>\n                        <boolProp name=\"Assertion.assume_success\">false</boolProp>\n                        <intProp name=\"Assertion.test_type\">2</intProp>\n                        <stringProp name=\"Assertion.custom_message\"></stringProp>\n                      </ResponseAssertion>\n                      <hashTree/>\n                      <RegexExtractor guiclass=\"RegexExtractorGui\" testclass=\"RegexExtractor\" testname=\"RegExp Extractor - Get Forum id\" enabled=\"false\">\n                        <stringProp name=\"RegexExtractor.useHeaders\">false</stringProp>\n                        <stringProp name=\"RegexExtractor.refname\">forumid</stringProp>\n                        <stringProp name=\"RegexExtractor.regex\">name=&quot;forum&quot; value=&quot;(\\d+)&quot;</stringProp>\n                        <stringProp name=\"RegexExtractor.template\">$1$</stringProp>\n                        <stringProp name=\"RegexExtractor.default\">NULL</stringProp>\n                        <stringProp name=\"RegexExtractor.match_number\"></stringProp>\n                      </RegexExtractor>\n                      <hashTree/>\n                      <XPathExtractor guiclass=\"XPathExtractorGui\" testclass=\"XPathExtractor\" testname=\"XPath Extractor - Get forum id\" enabled=\"false\">\n                        <stringProp name=\"XPathExtractor.default\"></stringProp>\n                        <stringProp name=\"XPathExtractor.refname\">forumid</stringProp>\n                        <stringProp name=\"XPathExtractor.xpathQuery\">//*[@name=&apos;forum&apos;][1]/@value</stringProp>\n                        <boolProp name=\"XPathExtractor.validate\">false</boolProp>\n                        <boolProp name=\"XPathExtractor.tolerant\">true</boolProp>\n                        <boolProp name=\"XPathExtractor.namespace\">false</boolProp>\n                        <boolProp name=\"XPathExtractor.show_warnings\">true</boolProp>\n                        <boolProp name=\"XPathExtractor.report_errors\">true</boolProp>\n                        <boolProp name=\"XPathExtractor.quiet\">false</boolProp>\n                        <stringProp name=\"XPathExtractor.matchNumber\">-1</stringProp>\n                      </XPathExtractor>\n                      <hashTree/>\n                      <ConstantTimer guiclass=\"ConstantTimerGui\" testclass=\"ConstantTimer\" testname=\"Constant Timer\" enabled=\"false\">\n                        <stringProp name=\"ConstantTimer.delay\">1000</stringProp>\n                      </ConstantTimer>\n                      <hashTree/>\n                      <GaussianRandomTimer guiclass=\"GaussianRandomTimerGui\" testclass=\"GaussianRandomTimer\" testname=\"Gaussian Random Timer\" enabled=\"true\">\n                        <stringProp name=\"ConstantTimer.delay\">3000</stringProp>\n                        <stringProp name=\"RandomTimer.range\">12000</stringProp>\n                      </GaussianRandomTimer>\n                      <hashTree/>\n                    </hashTree>\n                    <HTTPSamplerProxy guiclass=\"HttpTestSampleGui\" testclass=\"HTTPSamplerProxy\" testname=\"Forum View Discussion\" enabled=\"true\">\n                      <elementProp name=\"HTTPsampler.Arguments\" elementType=\"Arguments\" guiclass=\"HTTPArgumentsPanel\" testclass=\"Arguments\" testname=\"Argument List\" enabled=\"true\">\n                        <collectionProp name=\"Arguments.arguments\">\n                          <elementProp name=\"d\" elementType=\"HTTPArgument\">\n                            <boolProp name=\"HTTPArgument.always_encode\">false</boolProp>\n                            <stringProp name=\"Argument.value\">${discuss_id}</stringProp>\n                            <stringProp name=\"Argument.name\">d</stringProp>\n                            <stringProp name=\"Argument.metadata\">=</stringProp>\n                            <stringProp name=\"Argument.use_equals\">true</stringProp>\n                            <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                          </elementProp>\n                        </collectionProp>\n                      </elementProp>\n                      <stringProp name=\"HTTPSampler.domain\">${host}</stringProp>\n                      <stringProp name=\"HTTPSampler.port\">${port}</stringProp>\n                      <stringProp name=\"HTTPSampler.protocol\">${protocol}</stringProp>\n                      <stringProp name=\"HTTPSampler.contentEncoding\"></stringProp>\n                      <stringProp name=\"HTTPSampler.path\">/mod/forum/discuss.php</stringProp>\n                      <stringProp name=\"HTTPSampler.method\">GET</stringProp>\n                      <boolProp name=\"HTTPSampler.follow_redirects\">true</boolProp>\n                      <boolProp name=\"HTTPSampler.auto_redirects\">false</boolProp>\n                      <boolProp name=\"HTTPSampler.use_keepalive\">true</boolProp>\n                      <boolProp name=\"HTTPSampler.DO_MULTIPART_POST\">false</boolProp>\n                      <stringProp name=\"HTTPSampler.embedded_url_re\"></stringProp>\n                      <stringProp name=\"HTTPSampler.implementation\">Java</stringProp>\n                      <stringProp name=\"HTTPSampler.connect_timeout\"></stringProp>\n                      <stringProp name=\"HTTPSampler.response_timeout\"></stringProp>\n                      <stringProp name=\"TestPlan.comments\">View a forum discussion thread</stringProp>\n                    </HTTPSamplerProxy>\n                    <hashTree>\n                      <ResponseAssertion guiclass=\"AssertionGui\" testclass=\"ResponseAssertion\" testname=\"Assertion - in the discussion thread ?\" enabled=\"true\">\n                        <collectionProp name=\"Asserion.test_strings\">\n                          <stringProp name=\"1415837435\">${discuss_title}</stringProp>\n                          <stringProp name=\"-1185574437\">My Course list is</stringProp>\n                        </collectionProp>\n                        <stringProp name=\"Assertion.test_field\">Assertion.response_data</stringProp>\n                        <boolProp name=\"Assertion.assume_success\">false</boolProp>\n                        <intProp name=\"Assertion.test_type\">2</intProp>\n                        <stringProp name=\"Assertion.custom_message\"></stringProp>\n                      </ResponseAssertion>\n                      <hashTree/>\n                      <RegexExtractor guiclass=\"RegexExtractorGui\" testclass=\"RegexExtractor\" testname=\"RegExp Extractor - Get Forum id\" enabled=\"false\">\n                        <stringProp name=\"RegexExtractor.useHeaders\">false</stringProp>\n                        <stringProp name=\"RegexExtractor.refname\">forumid</stringProp>\n                        <stringProp name=\"RegexExtractor.regex\">name=&quot;forum&quot; value=&quot;(\\d+)&quot;</stringProp>\n                        <stringProp name=\"RegexExtractor.template\">$1$</stringProp>\n                        <stringProp name=\"RegexExtractor.default\">NULL</stringProp>\n                        <stringProp name=\"RegexExtractor.match_number\"></stringProp>\n                      </RegexExtractor>\n                      <hashTree/>\n                      <XPathExtractor guiclass=\"XPathExtractorGui\" testclass=\"XPathExtractor\" testname=\"XPath Extractor - Get forum id\" enabled=\"false\">\n                        <stringProp name=\"XPathExtractor.default\"></stringProp>\n                        <stringProp name=\"XPathExtractor.refname\">forumid</stringProp>\n                        <stringProp name=\"XPathExtractor.xpathQuery\">//*[@name=&apos;forum&apos;][1]/@value</stringProp>\n                        <boolProp name=\"XPathExtractor.validate\">false</boolProp>\n                        <boolProp name=\"XPathExtractor.tolerant\">true</boolProp>\n                        <boolProp name=\"XPathExtractor.namespace\">false</boolProp>\n                        <boolProp name=\"XPathExtractor.show_warnings\">true</boolProp>\n                        <boolProp name=\"XPathExtractor.report_errors\">true</boolProp>\n                        <boolProp name=\"XPathExtractor.quiet\">false</boolProp>\n                        <stringProp name=\"XPathExtractor.matchNumber\">-1</stringProp>\n                      </XPathExtractor>\n                      <hashTree/>\n                      <ConstantTimer guiclass=\"ConstantTimerGui\" testclass=\"ConstantTimer\" testname=\"Constant Timer\" enabled=\"false\">\n                        <stringProp name=\"ConstantTimer.delay\">1000</stringProp>\n                      </ConstantTimer>\n                      <hashTree/>\n                      <GaussianRandomTimer guiclass=\"GaussianRandomTimerGui\" testclass=\"GaussianRandomTimer\" testname=\"Gaussian Random Timer\" enabled=\"true\">\n                        <stringProp name=\"ConstantTimer.delay\">10000</stringProp>\n                        <stringProp name=\"RandomTimer.range\">4000</stringProp>\n                      </GaussianRandomTimer>\n                      <hashTree/>\n                    </hashTree>\n                  </hashTree>\n                </hashTree>\n                <IfController guiclass=\"IfControllerPanel\" testclass=\"IfController\" testname=\"Post a discussion (20% of the time)\" enabled=\"true\">\n                  <stringProp name=\"TestPlan.comments\">Only create a forum post (10% of the time a forum is viewed)</stringProp>\n                  <stringProp name=\"IfController.condition\">${__Random(1,20,ReuseRandomNumber)} &lt;= 20</stringProp>\n                  <boolProp name=\"IfController.evaluateAll\">false</boolProp>\n                </IfController>\n                <hashTree>\n                  <HTTPSamplerProxy guiclass=\"HttpTestSampleGui\" testclass=\"HTTPSamplerProxy\" testname=\"Click Add a New Discussion Topic\" enabled=\"true\">\n                    <elementProp name=\"HTTPsampler.Arguments\" elementType=\"Arguments\" guiclass=\"HTTPArgumentsPanel\" testclass=\"Arguments\" testname=\"Argument List\" enabled=\"true\">\n                      <collectionProp name=\"Arguments.arguments\">\n                        <elementProp name=\"forum\" elementType=\"HTTPArgument\">\n                          <boolProp name=\"HTTPArgument.always_encode\">false</boolProp>\n                          <stringProp name=\"Argument.value\">${forumid}</stringProp>\n                          <stringProp name=\"Argument.name\">forum</stringProp>\n                          <stringProp name=\"Argument.metadata\">=</stringProp>\n                          <stringProp name=\"Argument.use_equals\">true</stringProp>\n                          <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                        </elementProp>\n                      </collectionProp>\n                    </elementProp>\n                    <stringProp name=\"HTTPSampler.domain\">${host}</stringProp>\n                    <stringProp name=\"HTTPSampler.port\">${port}</stringProp>\n                    <stringProp name=\"HTTPSampler.protocol\">${protocol}</stringProp>\n                    <stringProp name=\"HTTPSampler.contentEncoding\"></stringProp>\n                    <stringProp name=\"HTTPSampler.path\">/mod/forum/post.php</stringProp>\n                    <stringProp name=\"HTTPSampler.method\">GET</stringProp>\n                    <boolProp name=\"HTTPSampler.follow_redirects\">true</boolProp>\n                    <boolProp name=\"HTTPSampler.auto_redirects\">false</boolProp>\n                    <boolProp name=\"HTTPSampler.use_keepalive\">true</boolProp>\n                    <boolProp name=\"HTTPSampler.DO_MULTIPART_POST\">false</boolProp>\n                    <stringProp name=\"HTTPSampler.embedded_url_re\"></stringProp>\n                    <stringProp name=\"HTTPSampler.implementation\">Java</stringProp>\n                    <stringProp name=\"HTTPSampler.connect_timeout\"></stringProp>\n                    <stringProp name=\"HTTPSampler.response_timeout\"></stringProp>\n                  </HTTPSamplerProxy>\n                  <hashTree>\n                    <ResponseAssertion guiclass=\"AssertionGui\" testclass=\"ResponseAssertion\" testname=\"Assertion - in the forum ?\" enabled=\"true\">\n                      <collectionProp name=\"Asserion.test_strings\">\n                        <stringProp name=\"1604416356\">Your new discussion topic</stringProp>\n                      </collectionProp>\n                      <stringProp name=\"Assertion.test_field\">Assertion.response_data</stringProp>\n                      <boolProp name=\"Assertion.assume_success\">false</boolProp>\n                      <intProp name=\"Assertion.test_type\">2</intProp>\n                      <stringProp name=\"Assertion.custom_message\"></stringProp>\n                    </ResponseAssertion>\n                    <hashTree/>\n                    <XPathExtractor guiclass=\"XPathExtractorGui\" testclass=\"XPathExtractor\" testname=\"XPath - Get discussion id\" enabled=\"false\">\n                      <stringProp name=\"XPathExtractor.default\"></stringProp>\n                      <stringProp name=\"XPathExtractor.refname\">discussionid</stringProp>\n                      <stringProp name=\"XPathExtractor.xpathQuery\">//*[@name=&apos;discussion&apos;][1]/@value</stringProp>\n                      <boolProp name=\"XPathExtractor.validate\">false</boolProp>\n                      <boolProp name=\"XPathExtractor.tolerant\">true</boolProp>\n                      <boolProp name=\"XPathExtractor.namespace\">false</boolProp>\n                      <boolProp name=\"XPathExtractor.show_warnings\">true</boolProp>\n                      <boolProp name=\"XPathExtractor.report_errors\">true</boolProp>\n                      <boolProp name=\"XPathExtractor.quiet\">false</boolProp>\n                      <stringProp name=\"XPathExtractor.matchNumber\">-1</stringProp>\n                    </XPathExtractor>\n                    <hashTree/>\n                    <RegexExtractor guiclass=\"RegexExtractorGui\" testclass=\"RegexExtractor\" testname=\"RegExp - Get discussion id\" enabled=\"true\">\n                      <stringProp name=\"RegexExtractor.useHeaders\">false</stringProp>\n                      <stringProp name=\"RegexExtractor.refname\">discussionid</stringProp>\n                      <stringProp name=\"RegexExtractor.regex\">name=&quot;discussion&quot;.*?value=&quot;([\\d]+)&quot;</stringProp>\n                      <stringProp name=\"RegexExtractor.template\">$1$</stringProp>\n                      <stringProp name=\"RegexExtractor.default\">NULL</stringProp>\n                      <stringProp name=\"RegexExtractor.match_number\">1</stringProp>\n                    </RegexExtractor>\n                    <hashTree/>\n                    <XPathExtractor guiclass=\"XPathExtractorGui\" testclass=\"XPathExtractor\" testname=\"XPath - Get parent id\" enabled=\"false\">\n                      <stringProp name=\"XPathExtractor.default\"></stringProp>\n                      <stringProp name=\"XPathExtractor.refname\">parentid</stringProp>\n                      <stringProp name=\"XPathExtractor.xpathQuery\">//*[@name=&apos;parent&apos;][1]/@value</stringProp>\n                      <boolProp name=\"XPathExtractor.validate\">false</boolProp>\n                      <boolProp name=\"XPathExtractor.tolerant\">true</boolProp>\n                      <boolProp name=\"XPathExtractor.namespace\">false</boolProp>\n                      <boolProp name=\"XPathExtractor.show_warnings\">true</boolProp>\n                      <boolProp name=\"XPathExtractor.report_errors\">true</boolProp>\n                      <boolProp name=\"XPathExtractor.quiet\">false</boolProp>\n                      <stringProp name=\"XPathExtractor.matchNumber\">-1</stringProp>\n                    </XPathExtractor>\n                    <hashTree/>\n                    <RegexExtractor guiclass=\"RegexExtractorGui\" testclass=\"RegexExtractor\" testname=\"RegExp - Get parent id\" enabled=\"true\">\n                      <stringProp name=\"RegexExtractor.useHeaders\">false</stringProp>\n                      <stringProp name=\"RegexExtractor.refname\">parentid</stringProp>\n                      <stringProp name=\"RegexExtractor.regex\">name=&quot;parent&quot;.*?value=&quot;([\\d]+)&quot;</stringProp>\n                      <stringProp name=\"RegexExtractor.template\">$1$</stringProp>\n                      <stringProp name=\"RegexExtractor.default\">NULL</stringProp>\n                      <stringProp name=\"RegexExtractor.match_number\">1</stringProp>\n                    </RegexExtractor>\n                    <hashTree/>\n                    <XPathExtractor guiclass=\"XPathExtractorGui\" testclass=\"XPathExtractor\" testname=\"XPath - Get user id\" enabled=\"false\">\n                      <stringProp name=\"XPathExtractor.default\"></stringProp>\n                      <stringProp name=\"XPathExtractor.refname\">userid</stringProp>\n                      <stringProp name=\"XPathExtractor.xpathQuery\">//*[@name=&apos;userid&apos;][1]/@value</stringProp>\n                      <boolProp name=\"XPathExtractor.validate\">false</boolProp>\n                      <boolProp name=\"XPathExtractor.tolerant\">true</boolProp>\n                      <boolProp name=\"XPathExtractor.namespace\">false</boolProp>\n                      <boolProp name=\"XPathExtractor.show_warnings\">true</boolProp>\n                      <boolProp name=\"XPathExtractor.report_errors\">true</boolProp>\n                      <boolProp name=\"XPathExtractor.quiet\">false</boolProp>\n                      <stringProp name=\"XPathExtractor.matchNumber\">-1</stringProp>\n                    </XPathExtractor>\n                    <hashTree/>\n                    <RegexExtractor guiclass=\"RegexExtractorGui\" testclass=\"RegexExtractor\" testname=\"RegExp - Get user id\" enabled=\"true\">\n                      <stringProp name=\"RegexExtractor.useHeaders\">false</stringProp>\n                      <stringProp name=\"RegexExtractor.refname\">userid</stringProp>\n                      <stringProp name=\"RegexExtractor.regex\">name=&quot;userid&quot;.*?value=&quot;([\\d]+)&quot;</stringProp>\n                      <stringProp name=\"RegexExtractor.template\">$1$</stringProp>\n                      <stringProp name=\"RegexExtractor.default\">NULL</stringProp>\n                      <stringProp name=\"RegexExtractor.match_number\">1</stringProp>\n                    </RegexExtractor>\n                    <hashTree/>\n                    <XPathExtractor guiclass=\"XPathExtractorGui\" testclass=\"XPathExtractor\" testname=\"XPath - Get group id\" enabled=\"false\">\n                      <stringProp name=\"XPathExtractor.default\"></stringProp>\n                      <stringProp name=\"XPathExtractor.refname\">groupid</stringProp>\n                      <stringProp name=\"XPathExtractor.xpathQuery\">//*[@name=&apos;groupid&apos;][1]/@value</stringProp>\n                      <boolProp name=\"XPathExtractor.validate\">false</boolProp>\n                      <boolProp name=\"XPathExtractor.tolerant\">true</boolProp>\n                      <boolProp name=\"XPathExtractor.namespace\">false</boolProp>\n                      <boolProp name=\"XPathExtractor.show_warnings\">true</boolProp>\n                      <boolProp name=\"XPathExtractor.report_errors\">true</boolProp>\n                      <boolProp name=\"XPathExtractor.quiet\">false</boolProp>\n                      <stringProp name=\"XPathExtractor.matchNumber\">-1</stringProp>\n                    </XPathExtractor>\n                    <hashTree/>\n                    <RegexExtractor guiclass=\"RegexExtractorGui\" testclass=\"RegexExtractor\" testname=\"RegExp - Get group id\" enabled=\"true\">\n                      <stringProp name=\"RegexExtractor.useHeaders\">false</stringProp>\n                      <stringProp name=\"RegexExtractor.refname\">groupid</stringProp>\n                      <stringProp name=\"RegexExtractor.regex\">name=&quot;groupid&quot;.*?value=&quot;([\\d]+)&quot;</stringProp>\n                      <stringProp name=\"RegexExtractor.template\">$1$</stringProp>\n                      <stringProp name=\"RegexExtractor.default\">NULL</stringProp>\n                      <stringProp name=\"RegexExtractor.match_number\">1</stringProp>\n                    </RegexExtractor>\n                    <hashTree/>\n                    <XPathExtractor guiclass=\"XPathExtractorGui\" testclass=\"XPathExtractor\" testname=\"XPath - Get edit id\" enabled=\"false\">\n                      <stringProp name=\"XPathExtractor.default\"></stringProp>\n                      <stringProp name=\"XPathExtractor.refname\">editid</stringProp>\n                      <stringProp name=\"XPathExtractor.xpathQuery\">//*[@name=&apos;edit&apos;][1]/@value</stringProp>\n                      <boolProp name=\"XPathExtractor.validate\">false</boolProp>\n                      <boolProp name=\"XPathExtractor.tolerant\">true</boolProp>\n                      <boolProp name=\"XPathExtractor.namespace\">false</boolProp>\n                      <boolProp name=\"XPathExtractor.show_warnings\">true</boolProp>\n                      <boolProp name=\"XPathExtractor.report_errors\">true</boolProp>\n                      <boolProp name=\"XPathExtractor.quiet\">false</boolProp>\n                      <stringProp name=\"XPathExtractor.matchNumber\">-1</stringProp>\n                    </XPathExtractor>\n                    <hashTree/>\n                    <RegexExtractor guiclass=\"RegexExtractorGui\" testclass=\"RegexExtractor\" testname=\"RegExp - Get edit id\" enabled=\"true\">\n                      <stringProp name=\"RegexExtractor.useHeaders\">false</stringProp>\n                      <stringProp name=\"RegexExtractor.refname\">editid</stringProp>\n                      <stringProp name=\"RegexExtractor.regex\">name=&quot;edit&quot;.*?value=&quot;([\\d]+)&quot;</stringProp>\n                      <stringProp name=\"RegexExtractor.template\">$1$</stringProp>\n                      <stringProp name=\"RegexExtractor.default\">NULL</stringProp>\n                      <stringProp name=\"RegexExtractor.match_number\">1</stringProp>\n                    </RegexExtractor>\n                    <hashTree/>\n                    <XPathExtractor guiclass=\"XPathExtractorGui\" testclass=\"XPathExtractor\" testname=\"XPath - Get attachment id\" enabled=\"false\">\n                      <stringProp name=\"XPathExtractor.default\"></stringProp>\n                      <stringProp name=\"XPathExtractor.refname\">attachmentid</stringProp>\n                      <stringProp name=\"XPathExtractor.xpathQuery\">//*[@name=&apos;attachment&apos;][1]/@value</stringProp>\n                      <boolProp name=\"XPathExtractor.validate\">false</boolProp>\n                      <boolProp name=\"XPathExtractor.tolerant\">true</boolProp>\n                      <boolProp name=\"XPathExtractor.namespace\">false</boolProp>\n                      <boolProp name=\"XPathExtractor.show_warnings\">true</boolProp>\n                      <boolProp name=\"XPathExtractor.report_errors\">true</boolProp>\n                      <boolProp name=\"XPathExtractor.quiet\">false</boolProp>\n                      <stringProp name=\"XPathExtractor.matchNumber\">-1</stringProp>\n                    </XPathExtractor>\n                    <hashTree/>\n                    <RegexExtractor guiclass=\"RegexExtractorGui\" testclass=\"RegexExtractor\" testname=\"RegExp - Get attachment id\" enabled=\"true\">\n                      <stringProp name=\"RegexExtractor.useHeaders\">false</stringProp>\n                      <stringProp name=\"RegexExtractor.refname\">attachmentid</stringProp>\n                      <stringProp name=\"RegexExtractor.regex\">value=&quot;([\\d]+)&quot;.*?name=&quot;attachments&quot;</stringProp>\n                      <stringProp name=\"RegexExtractor.template\">$1$</stringProp>\n                      <stringProp name=\"RegexExtractor.default\">NULL</stringProp>\n                      <stringProp name=\"RegexExtractor.match_number\">1</stringProp>\n                    </RegexExtractor>\n                    <hashTree/>\n                    <RegexExtractor guiclass=\"RegexExtractorGui\" testclass=\"RegexExtractor\" testname=\"RegExp - Get ctx id\" enabled=\"true\">\n                      <stringProp name=\"RegexExtractor.useHeaders\">false</stringProp>\n                      <stringProp name=\"RegexExtractor.refname\">ctxid</stringProp>\n                      <stringProp name=\"RegexExtractor.regex\">ctx_id=([\\d]+)</stringProp>\n                      <stringProp name=\"RegexExtractor.template\">$1$</stringProp>\n                      <stringProp name=\"RegexExtractor.default\">NULL</stringProp>\n                      <stringProp name=\"RegexExtractor.match_number\">1</stringProp>\n                    </RegexExtractor>\n                    <hashTree/>\n                    <RegexExtractor guiclass=\"RegexExtractorGui\" testclass=\"RegexExtractor\" testname=\"RegExp - Get client id\" enabled=\"true\">\n                      <stringProp name=\"RegexExtractor.useHeaders\">false</stringProp>\n                      <stringProp name=\"RegexExtractor.refname\">clientid</stringProp>\n                      <stringProp name=\"RegexExtractor.regex\">&quot;client_id&quot;:&quot;([0-9a-f]+)&quot;</stringProp>\n                      <stringProp name=\"RegexExtractor.template\">$1$</stringProp>\n                      <stringProp name=\"RegexExtractor.default\">NULL</stringProp>\n                      <stringProp name=\"RegexExtractor.match_number\">1</stringProp>\n                    </RegexExtractor>\n                    <hashTree/>\n                    <ConstantTimer guiclass=\"ConstantTimerGui\" testclass=\"ConstantTimer\" testname=\"Constant Timer\" enabled=\"false\">\n                      <stringProp name=\"ConstantTimer.delay\">1000</stringProp>\n                    </ConstantTimer>\n                    <hashTree/>\n                    <GaussianRandomTimer guiclass=\"GaussianRandomTimerGui\" testclass=\"GaussianRandomTimer\" testname=\"Gaussian Random Timer\" enabled=\"true\">\n                      <stringProp name=\"ConstantTimer.delay\">4000</stringProp>\n                      <stringProp name=\"RandomTimer.range\">3000</stringProp>\n                    </GaussianRandomTimer>\n                    <hashTree/>\n                    <XPathExtractor guiclass=\"XPathExtractorGui\" testclass=\"XPathExtractor\" testname=\"XPath - Get message itemid\" enabled=\"false\">\n                      <stringProp name=\"XPathExtractor.default\"></stringProp>\n                      <stringProp name=\"XPathExtractor.refname\">messageitemid</stringProp>\n                      <stringProp name=\"XPathExtractor.xpathQuery\">//*[@name=&apos;message[itemid]&apos;][1]/@value</stringProp>\n                      <boolProp name=\"XPathExtractor.validate\">false</boolProp>\n                      <boolProp name=\"XPathExtractor.tolerant\">true</boolProp>\n                      <boolProp name=\"XPathExtractor.namespace\">false</boolProp>\n                      <boolProp name=\"XPathExtractor.show_warnings\">true</boolProp>\n                      <boolProp name=\"XPathExtractor.report_errors\">true</boolProp>\n                      <boolProp name=\"XPathExtractor.quiet\">false</boolProp>\n                      <stringProp name=\"XPathExtractor.matchNumber\">-1</stringProp>\n                    </XPathExtractor>\n                    <hashTree/>\n                    <RegexExtractor guiclass=\"RegexExtractorGui\" testclass=\"RegexExtractor\" testname=\"RegExp - Get message itemid\" enabled=\"true\">\n                      <stringProp name=\"RegexExtractor.useHeaders\">false</stringProp>\n                      <stringProp name=\"RegexExtractor.refname\">messageitemid</stringProp>\n                      <stringProp name=\"RegexExtractor.regex\">name=&quot;message\\[itemid\\]&quot;.*?value=&quot;([\\d]+)&quot;</stringProp>\n                      <stringProp name=\"RegexExtractor.template\">$1$</stringProp>\n                      <stringProp name=\"RegexExtractor.default\">NULL</stringProp>\n                      <stringProp name=\"RegexExtractor.match_number\">1</stringProp>\n                    </RegexExtractor>\n                    <hashTree/>\n                  </hashTree>\n                  <HTTPSamplerProxy guiclass=\"HttpTestSampleGui\" testclass=\"HTTPSamplerProxy\" testname=\"Create a new topic\" enabled=\"true\">\n                    <elementProp name=\"HTTPsampler.Arguments\" elementType=\"Arguments\" guiclass=\"HTTPArgumentsPanel\" testclass=\"Arguments\" testname=\"Argument List\" enabled=\"true\">\n                      <collectionProp name=\"Arguments.arguments\">\n                        <elementProp name=\"course\" elementType=\"HTTPArgument\">\n                          <stringProp name=\"HTTPArgument.always_encode\">true</stringProp>\n                          <stringProp name=\"Argument.value\">${activity_course_id}</stringProp>\n                          <stringProp name=\"Argument.name\">course</stringProp>\n                          <stringProp name=\"Argument.metadata\">=</stringProp>\n                          <stringProp name=\"Argument.use_equals\">true</stringProp>\n                          <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                        </elementProp>\n                        <elementProp name=\"forum\" elementType=\"HTTPArgument\">\n                          <stringProp name=\"HTTPArgument.always_encode\">true</stringProp>\n                          <stringProp name=\"Argument.value\">${forumid}</stringProp>\n                          <stringProp name=\"Argument.name\">forum</stringProp>\n                          <stringProp name=\"Argument.metadata\">=</stringProp>\n                          <stringProp name=\"Argument.use_equals\">true</stringProp>\n                          <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                        </elementProp>\n                        <elementProp name=\"discussion\" elementType=\"HTTPArgument\">\n                          <stringProp name=\"HTTPArgument.always_encode\">true</stringProp>\n                          <stringProp name=\"Argument.value\">${discussionid}</stringProp>\n                          <stringProp name=\"Argument.name\">discussion</stringProp>\n                          <stringProp name=\"Argument.metadata\">=</stringProp>\n                          <stringProp name=\"Argument.use_equals\">true</stringProp>\n                          <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                        </elementProp>\n                        <elementProp name=\"parent\" elementType=\"HTTPArgument\">\n                          <stringProp name=\"HTTPArgument.always_encode\">true</stringProp>\n                          <stringProp name=\"Argument.value\">${parentid}</stringProp>\n                          <stringProp name=\"Argument.name\">parent</stringProp>\n                          <stringProp name=\"Argument.metadata\">=</stringProp>\n                          <stringProp name=\"Argument.use_equals\">true</stringProp>\n                          <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                        </elementProp>\n                        <elementProp name=\"groupid\" elementType=\"HTTPArgument\">\n                          <stringProp name=\"HTTPArgument.always_encode\">true</stringProp>\n                          <stringProp name=\"Argument.value\">${groupid}</stringProp>\n                          <stringProp name=\"Argument.name\">groupid</stringProp>\n                          <stringProp name=\"Argument.metadata\">=</stringProp>\n                          <stringProp name=\"Argument.use_equals\">true</stringProp>\n                          <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                        </elementProp>\n                        <elementProp name=\"edit\" elementType=\"HTTPArgument\">\n                          <stringProp name=\"HTTPArgument.always_encode\">true</stringProp>\n                          <stringProp name=\"Argument.value\">${editid}</stringProp>\n                          <stringProp name=\"Argument.name\">edit</stringProp>\n                          <stringProp name=\"Argument.metadata\">=</stringProp>\n                          <stringProp name=\"Argument.use_equals\">true</stringProp>\n                          <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                        </elementProp>\n                        <elementProp name=\"reply\" elementType=\"HTTPArgument\">\n                          <stringProp name=\"HTTPArgument.always_encode\">true</stringProp>\n                          <stringProp name=\"Argument.value\">0</stringProp>\n                          <stringProp name=\"Argument.name\">reply</stringProp>\n                          <stringProp name=\"Argument.metadata\">=</stringProp>\n                          <stringProp name=\"Argument.use_equals\">true</stringProp>\n                          <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                        </elementProp>\n                        <elementProp name=\"sesskey\" elementType=\"HTTPArgument\">\n                          <stringProp name=\"HTTPArgument.always_encode\">true</stringProp>\n                          <stringProp name=\"Argument.value\">${sesskey}</stringProp>\n                          <stringProp name=\"Argument.name\">sesskey</stringProp>\n                          <stringProp name=\"Argument.metadata\">=</stringProp>\n                          <stringProp name=\"Argument.use_equals\">true</stringProp>\n                          <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                        </elementProp>\n                        <elementProp name=\"_qf__mod_forum_post_form\" elementType=\"HTTPArgument\">\n                          <stringProp name=\"HTTPArgument.always_encode\">true</stringProp>\n                          <stringProp name=\"Argument.value\">1</stringProp>\n                          <stringProp name=\"Argument.name\">_qf__mod_forum_post_form</stringProp>\n                          <stringProp name=\"Argument.metadata\">=</stringProp>\n                          <stringProp name=\"Argument.use_equals\">true</stringProp>\n                          <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                        </elementProp>\n                        <elementProp name=\"mform_isexpanded_id_general\" elementType=\"HTTPArgument\">\n                          <boolProp name=\"HTTPArgument.always_encode\">false</boolProp>\n                          <stringProp name=\"Argument.value\">1</stringProp>\n                          <stringProp name=\"Argument.metadata\">=</stringProp>\n                          <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                          <stringProp name=\"Argument.name\">mform_isexpanded_id_general</stringProp>\n                        </elementProp>\n                        <elementProp name=\"mform_isexpanded_id_displayperiod\" elementType=\"HTTPArgument\">\n                          <boolProp name=\"HTTPArgument.always_encode\">false</boolProp>\n                          <stringProp name=\"Argument.value\">0</stringProp>\n                          <stringProp name=\"Argument.metadata\">=</stringProp>\n                          <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                          <stringProp name=\"Argument.name\">mform_isexpanded_id_displayperiod</stringProp>\n                        </elementProp>\n                        <elementProp name=\"mform_isexpanded_id_tagshdr\" elementType=\"HTTPArgument\">\n                          <boolProp name=\"HTTPArgument.always_encode\">false</boolProp>\n                          <stringProp name=\"Argument.value\">0</stringProp>\n                          <stringProp name=\"Argument.metadata\">=</stringProp>\n                          <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                          <stringProp name=\"Argument.name\">mform_isexpanded_id_tagshdr</stringProp>\n                        </elementProp>\n                        <elementProp name=\"subject\" elementType=\"HTTPArgument\">\n                          <stringProp name=\"HTTPArgument.always_encode\">true</stringProp>\n                          <stringProp name=\"Argument.value\">${username} - ${RandomNumber}</stringProp>\n                          <stringProp name=\"Argument.name\">subject</stringProp>\n                          <stringProp name=\"Argument.metadata\">=</stringProp>\n                          <stringProp name=\"Argument.use_equals\">true</stringProp>\n                          <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                        </elementProp>\n                        <elementProp name=\"message[text]\" elementType=\"HTTPArgument\">\n                          <stringProp name=\"HTTPArgument.always_encode\">true</stringProp>\n                          <stringProp name=\"Argument.value\">&lt;p&gt;My Course list is ${courseList}&lt;/p&gt;</stringProp>\n                          <stringProp name=\"Argument.name\">message[text]</stringProp>\n                          <stringProp name=\"Argument.metadata\">=</stringProp>\n                          <stringProp name=\"Argument.use_equals\">true</stringProp>\n                          <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                        </elementProp>\n                        <elementProp name=\"message[format]\" elementType=\"HTTPArgument\">\n                          <boolProp name=\"HTTPArgument.always_encode\">false</boolProp>\n                          <stringProp name=\"Argument.value\">1</stringProp>\n                          <stringProp name=\"Argument.metadata\">=</stringProp>\n                          <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                          <stringProp name=\"Argument.name\">message[format]</stringProp>\n                        </elementProp>\n                        <elementProp name=\"message[itemid]\" elementType=\"HTTPArgument\">\n                          <boolProp name=\"HTTPArgument.always_encode\">false</boolProp>\n                          <stringProp name=\"Argument.value\">${messageitemid}</stringProp>\n                          <stringProp name=\"Argument.metadata\">=</stringProp>\n                          <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                          <stringProp name=\"Argument.name\">message[itemid]</stringProp>\n                        </elementProp>\n                        <elementProp name=\"discussionsubscribe\" elementType=\"HTTPArgument\">\n                          <boolProp name=\"HTTPArgument.always_encode\">false</boolProp>\n                          <stringProp name=\"Argument.value\">1</stringProp>\n                          <stringProp name=\"Argument.metadata\">=</stringProp>\n                          <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                          <stringProp name=\"Argument.name\">discussionsubscribe</stringProp>\n                        </elementProp>\n                        <elementProp name=\"attachments\" elementType=\"HTTPArgument\">\n                          <boolProp name=\"HTTPArgument.always_encode\">false</boolProp>\n                          <stringProp name=\"Argument.value\">${attachmentid}</stringProp>\n                          <stringProp name=\"Argument.metadata\">=</stringProp>\n                          <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                          <stringProp name=\"Argument.name\">attachments</stringProp>\n                        </elementProp>\n                        <elementProp name=\"tags\" elementType=\"HTTPArgument\">\n                          <boolProp name=\"HTTPArgument.always_encode\">false</boolProp>\n                          <stringProp name=\"Argument.value\">_qf__force_multiselect_submission</stringProp>\n                          <stringProp name=\"Argument.metadata\">=</stringProp>\n                          <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                          <stringProp name=\"Argument.name\">tags</stringProp>\n                        </elementProp>\n                        <elementProp name=\"submitbutton\" elementType=\"HTTPArgument\">\n                          <boolProp name=\"HTTPArgument.always_encode\">false</boolProp>\n                          <stringProp name=\"Argument.value\">Post to forum</stringProp>\n                          <stringProp name=\"Argument.metadata\">=</stringProp>\n                          <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                          <stringProp name=\"Argument.name\">submitbutton</stringProp>\n                        </elementProp>\n                      </collectionProp>\n                    </elementProp>\n                    <stringProp name=\"HTTPSampler.domain\">${host}</stringProp>\n                    <stringProp name=\"HTTPSampler.port\">${port}</stringProp>\n                    <stringProp name=\"HTTPSampler.protocol\">${protocol}</stringProp>\n                    <stringProp name=\"HTTPSampler.contentEncoding\"></stringProp>\n                    <stringProp name=\"HTTPSampler.path\">/mod/forum/post.php</stringProp>\n                    <stringProp name=\"HTTPSampler.method\">POST</stringProp>\n                    <boolProp name=\"HTTPSampler.follow_redirects\">true</boolProp>\n                    <boolProp name=\"HTTPSampler.auto_redirects\">false</boolProp>\n                    <boolProp name=\"HTTPSampler.use_keepalive\">true</boolProp>\n                    <boolProp name=\"HTTPSampler.DO_MULTIPART_POST\">true</boolProp>\n                    <stringProp name=\"HTTPSampler.embedded_url_re\"></stringProp>\n                    <stringProp name=\"HTTPSampler.implementation\">Java</stringProp>\n                    <stringProp name=\"HTTPSampler.connect_timeout\"></stringProp>\n                    <stringProp name=\"HTTPSampler.response_timeout\"></stringProp>\n                  </HTTPSamplerProxy>\n                  <hashTree>\n                    <ResponseAssertion guiclass=\"AssertionGui\" testclass=\"ResponseAssertion\" testname=\"Assertion - Response Contains Your post was successfully\" enabled=\"true\">\n                      <collectionProp name=\"Asserion.test_strings\">\n                        <stringProp name=\"-2123111183\">Your post was successfully</stringProp>\n                      </collectionProp>\n                      <intProp name=\"Assertion.test_type\">2</intProp>\n                      <stringProp name=\"Assertion.test_field\">Assertion.response_data</stringProp>\n                      <boolProp name=\"Assertion.assume_success\">false</boolProp>\n                      <stringProp name=\"Assertion.custom_message\"></stringProp>\n                    </ResponseAssertion>\n                    <hashTree/>\n                    <ConstantTimer guiclass=\"ConstantTimerGui\" testclass=\"ConstantTimer\" testname=\"Constant Timer\" enabled=\"true\">\n                      <stringProp name=\"ConstantTimer.delay\">1000</stringProp>\n                    </ConstantTimer>\n                    <hashTree/>\n                  </hashTree>\n                  <HTTPSamplerProxy guiclass=\"HttpTestSampleGui\" testclass=\"HTTPSamplerProxy\" testname=\"Refresh view of Forum Module; check is updated\" enabled=\"true\">\n                    <elementProp name=\"HTTPsampler.Arguments\" elementType=\"Arguments\" guiclass=\"HTTPArgumentsPanel\" testclass=\"Arguments\" testname=\"Argument List\" enabled=\"true\">\n                      <collectionProp name=\"Arguments.arguments\">\n                        <elementProp name=\"id\" elementType=\"HTTPArgument\">\n                          <boolProp name=\"HTTPArgument.always_encode\">false</boolProp>\n                          <stringProp name=\"Argument.value\">${moduleid}</stringProp>\n                          <stringProp name=\"Argument.name\">id</stringProp>\n                          <stringProp name=\"Argument.metadata\">=</stringProp>\n                          <stringProp name=\"Argument.use_equals\">true</stringProp>\n                          <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                        </elementProp>\n                      </collectionProp>\n                    </elementProp>\n                    <stringProp name=\"HTTPSampler.domain\">${host}</stringProp>\n                    <stringProp name=\"HTTPSampler.port\">${port}</stringProp>\n                    <stringProp name=\"HTTPSampler.protocol\">${protocol}</stringProp>\n                    <stringProp name=\"HTTPSampler.contentEncoding\"></stringProp>\n                    <stringProp name=\"HTTPSampler.path\">/mod/forum/view.php</stringProp>\n                    <stringProp name=\"HTTPSampler.method\">GET</stringProp>\n                    <boolProp name=\"HTTPSampler.follow_redirects\">true</boolProp>\n                    <boolProp name=\"HTTPSampler.auto_redirects\">false</boolProp>\n                    <boolProp name=\"HTTPSampler.use_keepalive\">true</boolProp>\n                    <boolProp name=\"HTTPSampler.DO_MULTIPART_POST\">false</boolProp>\n                    <stringProp name=\"HTTPSampler.embedded_url_re\"></stringProp>\n                    <stringProp name=\"HTTPSampler.implementation\">Java</stringProp>\n                    <stringProp name=\"HTTPSampler.connect_timeout\"></stringProp>\n                    <stringProp name=\"HTTPSampler.response_timeout\"></stringProp>\n                  </HTTPSamplerProxy>\n                  <hashTree>\n                    <ResponseAssertion guiclass=\"AssertionGui\" testclass=\"ResponseAssertion\" testname=\"Assertion - in the forum module ?\" enabled=\"true\">\n                      <collectionProp name=\"Asserion.test_strings\">\n                        <stringProp name=\"1915803329\">Performance Testing Forum</stringProp>\n                      </collectionProp>\n                      <stringProp name=\"Assertion.test_field\">Assertion.response_data</stringProp>\n                      <boolProp name=\"Assertion.assume_success\">false</boolProp>\n                      <intProp name=\"Assertion.test_type\">2</intProp>\n                      <stringProp name=\"Assertion.custom_message\"></stringProp>\n                    </ResponseAssertion>\n                    <hashTree/>\n                    <XPathExtractor guiclass=\"XPathExtractorGui\" testclass=\"XPathExtractor\" testname=\"XPath - Get the newly-created discussion id\" enabled=\"false\">\n                      <stringProp name=\"XPathExtractor.default\"></stringProp>\n                      <stringProp name=\"XPathExtractor.refname\">discussionid</stringProp>\n                      <stringProp name=\"XPathExtractor.xpathQuery\">substring-after(//*/a[contains(text(),&apos;${username} - ${RandomNumber}&apos;)][1]/@href,&apos;=&apos;)</stringProp>\n                      <boolProp name=\"XPathExtractor.validate\">false</boolProp>\n                      <boolProp name=\"XPathExtractor.tolerant\">true</boolProp>\n                      <boolProp name=\"XPathExtractor.namespace\">false</boolProp>\n                      <boolProp name=\"XPathExtractor.show_warnings\">true</boolProp>\n                      <boolProp name=\"XPathExtractor.report_errors\">true</boolProp>\n                      <boolProp name=\"XPathExtractor.quiet\">false</boolProp>\n                      <stringProp name=\"XPathExtractor.matchNumber\">-1</stringProp>\n                    </XPathExtractor>\n                    <hashTree/>\n                    <RegexExtractor guiclass=\"RegexExtractorGui\" testclass=\"RegexExtractor\" testname=\"RegExp - Get the newly-created discussion id\" enabled=\"false\">\n                      <stringProp name=\"RegexExtractor.useHeaders\">false</stringProp>\n                      <stringProp name=\"RegexExtractor.refname\">discussionid</stringProp>\n                      <stringProp name=\"RegexExtractor.regex\">name=&quot;message\\[itemid\\]&quot;.*?value=&quot;([\\d]+)&quot;</stringProp>\n                      <stringProp name=\"RegexExtractor.template\">$1$</stringProp>\n                      <stringProp name=\"RegexExtractor.default\">NULL</stringProp>\n                      <stringProp name=\"RegexExtractor.match_number\">1</stringProp>\n                    </RegexExtractor>\n                    <hashTree/>\n                    <ConstantTimer guiclass=\"ConstantTimerGui\" testclass=\"ConstantTimer\" testname=\"Constant Timer\" enabled=\"false\">\n                      <stringProp name=\"ConstantTimer.delay\">1000</stringProp>\n                    </ConstantTimer>\n                    <hashTree/>\n                    <GaussianRandomTimer guiclass=\"GaussianRandomTimerGui\" testclass=\"GaussianRandomTimer\" testname=\"Gaussian Random Timer\" enabled=\"true\">\n                      <stringProp name=\"ConstantTimer.delay\">4000</stringProp>\n                      <stringProp name=\"RandomTimer.range\">3000</stringProp>\n                    </GaussianRandomTimer>\n                    <hashTree/>\n                  </hashTree>\n                  <DebugSampler guiclass=\"TestBeanGUI\" testclass=\"DebugSampler\" testname=\"Debug Sampler\" enabled=\"false\">\n                    <boolProp name=\"displayJMeterProperties\">false</boolProp>\n                    <boolProp name=\"displayJMeterVariables\">true</boolProp>\n                    <boolProp name=\"displaySystemProperties\">false</boolProp>\n                  </DebugSampler>\n                  <hashTree/>\n                  <ResultCollector guiclass=\"ViewResultsFullVisualizer\" testclass=\"ResultCollector\" testname=\"View Results Tree\" enabled=\"true\">\n                    <boolProp name=\"ResultCollector.error_logging\">false</boolProp>\n                    <objProp>\n                      <name>saveConfig</name>\n                      <value class=\"SampleSaveConfiguration\">\n                        <time>true</time>\n                        <latency>true</latency>\n                        <timestamp>true</timestamp>\n                        <success>true</success>\n                        <label>true</label>\n                        <code>true</code>\n                        <message>true</message>\n                        <threadName>true</threadName>\n                        <dataType>true</dataType>\n                        <encoding>false</encoding>\n                        <assertions>true</assertions>\n                        <subresults>true</subresults>\n                        <responseData>false</responseData>\n                        <samplerData>false</samplerData>\n                        <xml>false</xml>\n                        <fieldNames>false</fieldNames>\n                        <responseHeaders>false</responseHeaders>\n                        <requestHeaders>false</requestHeaders>\n                        <responseDataOnError>false</responseDataOnError>\n                        <saveAssertionResultsFailureMessage>false</saveAssertionResultsFailureMessage>\n                        <assertionsResultsToSave>0</assertionsResultsToSave>\n                        <bytes>true</bytes>\n                        <threadCounts>true</threadCounts>\n                      </value>\n                    </objProp>\n                    <stringProp name=\"filename\"></stringProp>\n                  </ResultCollector>\n                  <hashTree/>\n                  <ResultCollector guiclass=\"GraphVisualizer\" testclass=\"ResultCollector\" testname=\"Graph Results\" enabled=\"false\">\n                    <boolProp name=\"ResultCollector.error_logging\">false</boolProp>\n                    <objProp>\n                      <name>saveConfig</name>\n                      <value class=\"SampleSaveConfiguration\">\n                        <time>true</time>\n                        <latency>true</latency>\n                        <timestamp>true</timestamp>\n                        <success>true</success>\n                        <label>true</label>\n                        <code>true</code>\n                        <message>true</message>\n                        <threadName>true</threadName>\n                        <dataType>true</dataType>\n                        <encoding>false</encoding>\n                        <assertions>true</assertions>\n                        <subresults>true</subresults>\n                        <responseData>false</responseData>\n                        <samplerData>false</samplerData>\n                        <xml>true</xml>\n                        <fieldNames>false</fieldNames>\n                        <responseHeaders>false</responseHeaders>\n                        <requestHeaders>false</requestHeaders>\n                        <responseDataOnError>false</responseDataOnError>\n                        <saveAssertionResultsFailureMessage>false</saveAssertionResultsFailureMessage>\n                        <assertionsResultsToSave>0</assertionsResultsToSave>\n                        <bytes>true</bytes>\n                        <threadCounts>true</threadCounts>\n                      </value>\n                    </objProp>\n                    <stringProp name=\"filename\"></stringProp>\n                  </ResultCollector>\n                  <hashTree/>\n                </hashTree>\n              </hashTree>\n              <IfController guiclass=\"IfControllerPanel\" testclass=\"IfController\" testname=\"Quiz (30% frequency the time)\" enabled=\"true\">\n                <stringProp name=\"IfController.condition\">${__Random(1,10,ReuseRandomNumber)} &lt;= 10</stringProp>\n                <boolProp name=\"IfController.evaluateAll\">false</boolProp>\n                <stringProp name=\"TestPlan.comments\">30% of the time, do the quiz</stringProp>\n              </IfController>\n              <hashTree>\n                <HTTPSamplerProxy guiclass=\"HttpTestSampleGui\" testclass=\"HTTPSamplerProxy\" testname=\"Goto course\" enabled=\"true\">\n                  <elementProp name=\"HTTPsampler.Arguments\" elementType=\"Arguments\" guiclass=\"HTTPArgumentsPanel\" testclass=\"Arguments\" testname=\"Argument List\" enabled=\"true\">\n                    <collectionProp name=\"Arguments.arguments\">\n                      <elementProp name=\"id\" elementType=\"HTTPArgument\">\n                        <boolProp name=\"HTTPArgument.always_encode\">false</boolProp>\n                        <stringProp name=\"Argument.value\">${activity_course_id}</stringProp>\n                        <stringProp name=\"Argument.name\">id</stringProp>\n                        <stringProp name=\"Argument.metadata\">=</stringProp>\n                        <stringProp name=\"Argument.use_equals\">true</stringProp>\n                        <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                      </elementProp>\n                    </collectionProp>\n                  </elementProp>\n                  <stringProp name=\"HTTPSampler.domain\">${host}</stringProp>\n                  <stringProp name=\"HTTPSampler.port\">${port}</stringProp>\n                  <stringProp name=\"HTTPSampler.protocol\">${protocol}</stringProp>\n                  <stringProp name=\"HTTPSampler.contentEncoding\"></stringProp>\n                  <stringProp name=\"HTTPSampler.path\">/course/view.php</stringProp>\n                  <stringProp name=\"HTTPSampler.method\">GET</stringProp>\n                  <boolProp name=\"HTTPSampler.follow_redirects\">true</boolProp>\n                  <boolProp name=\"HTTPSampler.auto_redirects\">false</boolProp>\n                  <boolProp name=\"HTTPSampler.use_keepalive\">true</boolProp>\n                  <boolProp name=\"HTTPSampler.DO_MULTIPART_POST\">false</boolProp>\n                  <stringProp name=\"HTTPSampler.embedded_url_re\"></stringProp>\n                  <stringProp name=\"HTTPSampler.implementation\">Java</stringProp>\n                  <stringProp name=\"HTTPSampler.connect_timeout\"></stringProp>\n                  <stringProp name=\"HTTPSampler.response_timeout\"></stringProp>\n                </HTTPSamplerProxy>\n                <hashTree>\n                  <ResponseAssertion guiclass=\"AssertionGui\" testclass=\"ResponseAssertion\" testname=\"Assertion - in the course ?\" enabled=\"true\">\n                    <collectionProp name=\"Asserion.test_strings\">\n                      <stringProp name=\"-1205900583\">Performance Testing Regular Quiz</stringProp>\n                    </collectionProp>\n                    <intProp name=\"Assertion.test_type\">2</intProp>\n                    <stringProp name=\"Assertion.test_field\">Assertion.response_data</stringProp>\n                    <boolProp name=\"Assertion.assume_success\">false</boolProp>\n                    <stringProp name=\"Assertion.custom_message\"></stringProp>\n                  </ResponseAssertion>\n                  <hashTree/>\n                  <RegexExtractor guiclass=\"RegexExtractorGui\" testclass=\"RegexExtractor\" testname=\"RegExp Extractor - Get forum discussion headings (if any!)\" enabled=\"false\">\n                    <stringProp name=\"RegexExtractor.useHeaders\">false</stringProp>\n                    <stringProp name=\"RegexExtractor.refname\">classtopics_html</stringProp>\n                    <stringProp name=\"RegexExtractor.regex\">(?s)(&lt;ul class=&quot;topics&quot;.*?)&lt;\\/div&gt;.*?&lt;\\/div&gt;.*?&lt;\\/div&gt;.*?&lt;\\/div&gt;.*?&lt;\\/div&gt;.*?&lt;div id=&quot;region-pre&quot;</stringProp>\n                    <stringProp name=\"RegexExtractor.template\">$1$</stringProp>\n                    <stringProp name=\"RegexExtractor.default\">NULL</stringProp>\n                    <stringProp name=\"RegexExtractor.match_number\"></stringProp>\n                  </RegexExtractor>\n                  <hashTree/>\n                  <RegexExtractor guiclass=\"RegexExtractorGui\" testclass=\"RegexExtractor\" testname=\"RegExp Extractor - Get perf quiz chunk\" enabled=\"true\">\n                    <stringProp name=\"RegexExtractor.useHeaders\">false</stringProp>\n                    <stringProp name=\"RegexExtractor.refname\">html_chunk_perfquiz</stringProp>\n                    <stringProp name=\"RegexExtractor.regex\">(.{0,250}Performance Testing Regular Quiz)</stringProp>\n                    <stringProp name=\"RegexExtractor.template\">$1$</stringProp>\n                    <stringProp name=\"RegexExtractor.default\">NULL</stringProp>\n                    <stringProp name=\"RegexExtractor.match_number\"></stringProp>\n                  </RegexExtractor>\n                  <hashTree/>\n                  <RegexExtractor guiclass=\"RegexExtractorGui\" testclass=\"RegexExtractor\" testname=\"RegExp Extractor - Get forum discussion headings (if any!)\" enabled=\"true\">\n                    <stringProp name=\"RegexExtractor.useHeaders\">false</stringProp>\n                    <stringProp name=\"RegexExtractor.refname\">moduleid</stringProp>\n                    <stringProp name=\"RegexExtractor.regex\">\\/mod\\/quiz\\/view\\.php\\?id=(\\d+)</stringProp>\n                    <stringProp name=\"RegexExtractor.template\">$1$</stringProp>\n                    <stringProp name=\"RegexExtractor.default\">NULL</stringProp>\n                    <stringProp name=\"RegexExtractor.match_number\"></stringProp>\n                    <stringProp name=\"Sample.scope\">variable</stringProp>\n                    <stringProp name=\"Scope.variable\">html_chunk_perfquiz</stringProp>\n                  </RegexExtractor>\n                  <hashTree/>\n                  <XPathExtractor guiclass=\"XPathExtractorGui\" testclass=\"XPathExtractor\" testname=\"XPath Extractor - Get Performance Test Regular Quiz id\" enabled=\"false\">\n                    <stringProp name=\"XPathExtractor.default\">NULL</stringProp>\n                    <stringProp name=\"XPathExtractor.refname\">moduleid</stringProp>\n                    <stringProp name=\"XPathExtractor.xpathQuery\">substring-after(//*[contains(text(),&apos;Performance Testing Regular Quiz&apos;)]/../@href,&apos;id=&apos;)</stringProp>\n                    <boolProp name=\"XPathExtractor.validate\">false</boolProp>\n                    <boolProp name=\"XPathExtractor.tolerant\">true</boolProp>\n                    <boolProp name=\"XPathExtractor.namespace\">false</boolProp>\n                    <boolProp name=\"XPathExtractor.show_warnings\">true</boolProp>\n                    <boolProp name=\"XPathExtractor.quiet\">false</boolProp>\n                    <boolProp name=\"XPathExtractor.report_errors\">true</boolProp>\n                    <stringProp name=\"Sample.scope\">variable</stringProp>\n                    <stringProp name=\"Scope.variable\">classtopics_html</stringProp>\n                    <stringProp name=\"XPathExtractor.matchNumber\">-1</stringProp>\n                  </XPathExtractor>\n                  <hashTree/>\n                  <XPathExtractor guiclass=\"XPathExtractorGui\" testclass=\"XPathExtractor\" testname=\"Get module id\" enabled=\"false\">\n                    <stringProp name=\"XPathExtractor.default\"></stringProp>\n                    <stringProp name=\"XPathExtractor.refname\">moduleid</stringProp>\n                    <stringProp name=\"XPathExtractor.xpathQuery\">substring-after(//*/a/*[contains(text(),&apos;Performance Testing Regular Quiz&apos;)]/../@href,&apos;=&apos;)</stringProp>\n                    <boolProp name=\"XPathExtractor.validate\">false</boolProp>\n                    <boolProp name=\"XPathExtractor.tolerant\">true</boolProp>\n                    <boolProp name=\"XPathExtractor.namespace\">false</boolProp>\n                    <boolProp name=\"XPathExtractor.show_warnings\">true</boolProp>\n                    <boolProp name=\"XPathExtractor.report_errors\">true</boolProp>\n                    <boolProp name=\"XPathExtractor.quiet\">false</boolProp>\n                    <stringProp name=\"XPathExtractor.matchNumber\">-1</stringProp>\n                  </XPathExtractor>\n                  <hashTree/>\n                  <GaussianRandomTimer guiclass=\"GaussianRandomTimerGui\" testclass=\"GaussianRandomTimer\" testname=\"Gaussian Random Timer\" enabled=\"true\">\n                    <stringProp name=\"ConstantTimer.delay\">4000</stringProp>\n                    <stringProp name=\"RandomTimer.range\">3000</stringProp>\n                  </GaussianRandomTimer>\n                  <hashTree/>\n                  <ConstantTimer guiclass=\"ConstantTimerGui\" testclass=\"ConstantTimer\" testname=\"Constant Timer\" enabled=\"false\">\n                    <stringProp name=\"ConstantTimer.delay\">1000</stringProp>\n                  </ConstantTimer>\n                  <hashTree/>\n                </hashTree>\n                <HTTPSamplerProxy guiclass=\"HttpTestSampleGui\" testclass=\"HTTPSamplerProxy\" testname=\"Open the quiz\" enabled=\"true\">\n                  <elementProp name=\"HTTPsampler.Arguments\" elementType=\"Arguments\" guiclass=\"HTTPArgumentsPanel\" testclass=\"Arguments\" testname=\"Argument List\" enabled=\"true\">\n                    <collectionProp name=\"Arguments.arguments\">\n                      <elementProp name=\"id\" elementType=\"HTTPArgument\">\n                        <boolProp name=\"HTTPArgument.always_encode\">false</boolProp>\n                        <stringProp name=\"Argument.value\">${moduleid}</stringProp>\n                        <stringProp name=\"Argument.name\">id</stringProp>\n                        <stringProp name=\"Argument.metadata\">=</stringProp>\n                        <stringProp name=\"Argument.use_equals\">true</stringProp>\n                        <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                      </elementProp>\n                    </collectionProp>\n                  </elementProp>\n                  <stringProp name=\"HTTPSampler.domain\">${host}</stringProp>\n                  <stringProp name=\"HTTPSampler.port\">${port}</stringProp>\n                  <stringProp name=\"HTTPSampler.protocol\">${protocol}</stringProp>\n                  <stringProp name=\"HTTPSampler.contentEncoding\"></stringProp>\n                  <stringProp name=\"HTTPSampler.path\">/mod/quiz/view.php</stringProp>\n                  <stringProp name=\"HTTPSampler.method\">GET</stringProp>\n                  <boolProp name=\"HTTPSampler.follow_redirects\">true</boolProp>\n                  <boolProp name=\"HTTPSampler.auto_redirects\">false</boolProp>\n                  <boolProp name=\"HTTPSampler.use_keepalive\">true</boolProp>\n                  <boolProp name=\"HTTPSampler.DO_MULTIPART_POST\">false</boolProp>\n                  <stringProp name=\"HTTPSampler.embedded_url_re\"></stringProp>\n                  <stringProp name=\"HTTPSampler.implementation\">Java</stringProp>\n                  <stringProp name=\"HTTPSampler.connect_timeout\"></stringProp>\n                  <stringProp name=\"HTTPSampler.response_timeout\"></stringProp>\n                </HTTPSamplerProxy>\n                <hashTree>\n                  <ResponseAssertion guiclass=\"AssertionGui\" testclass=\"ResponseAssertion\" testname=\"Assertion - in the quiz entry screen?\" enabled=\"true\">\n                    <collectionProp name=\"Asserion.test_strings\">\n                      <stringProp name=\"-1205900583\">Performance Testing Regular Quiz</stringProp>\n                    </collectionProp>\n                    <stringProp name=\"Assertion.test_field\">Assertion.response_data</stringProp>\n                    <boolProp name=\"Assertion.assume_success\">false</boolProp>\n                    <intProp name=\"Assertion.test_type\">2</intProp>\n                    <stringProp name=\"Assertion.custom_message\"></stringProp>\n                  </ResponseAssertion>\n                  <hashTree/>\n                  <XPathExtractor guiclass=\"XPathExtractorGui\" testclass=\"XPathExtractor\" testname=\"Get quiz id\" enabled=\"false\">\n                    <stringProp name=\"XPathExtractor.default\"></stringProp>\n                    <stringProp name=\"XPathExtractor.refname\">quizid</stringProp>\n                    <stringProp name=\"XPathExtractor.xpathQuery\">//*/form[@action=&quot;https://${host}/mod/quiz/startattempt.php&quot;]//*/input[@name=&apos;cmid&apos;][1]/@value</stringProp>\n                    <boolProp name=\"XPathExtractor.validate\">false</boolProp>\n                    <boolProp name=\"XPathExtractor.tolerant\">true</boolProp>\n                    <boolProp name=\"XPathExtractor.namespace\">false</boolProp>\n                    <boolProp name=\"XPathExtractor.show_warnings\">true</boolProp>\n                    <boolProp name=\"XPathExtractor.report_errors\">true</boolProp>\n                    <boolProp name=\"XPathExtractor.quiet\">false</boolProp>\n                    <stringProp name=\"XPathExtractor.matchNumber\">-1</stringProp>\n                  </XPathExtractor>\n                  <hashTree/>\n                  <GaussianRandomTimer guiclass=\"GaussianRandomTimerGui\" testclass=\"GaussianRandomTimer\" testname=\"Gaussian Random Timer\" enabled=\"true\">\n                    <stringProp name=\"ConstantTimer.delay\">10000</stringProp>\n                    <stringProp name=\"RandomTimer.range\">3000</stringProp>\n                  </GaussianRandomTimer>\n                  <hashTree/>\n                  <ConstantTimer guiclass=\"ConstantTimerGui\" testclass=\"ConstantTimer\" testname=\"Constant Timer\" enabled=\"false\">\n                    <stringProp name=\"ConstantTimer.delay\">1000</stringProp>\n                  </ConstantTimer>\n                  <hashTree/>\n                </hashTree>\n                <HTTPSamplerProxy guiclass=\"HttpTestSampleGui\" testclass=\"HTTPSamplerProxy\" testname=\"Click on Attempt the quiz now\" enabled=\"true\">\n                  <elementProp name=\"HTTPsampler.Arguments\" elementType=\"Arguments\" guiclass=\"HTTPArgumentsPanel\" testclass=\"Arguments\" testname=\"Argument List\" enabled=\"true\">\n                    <collectionProp name=\"Arguments.arguments\">\n                      <elementProp name=\"cmid\" elementType=\"HTTPArgument\">\n                        <boolProp name=\"HTTPArgument.always_encode\">false</boolProp>\n                        <stringProp name=\"Argument.value\">${moduleid}</stringProp>\n                        <stringProp name=\"Argument.name\">cmid</stringProp>\n                        <stringProp name=\"Argument.metadata\">=</stringProp>\n                        <stringProp name=\"Argument.use_equals\">true</stringProp>\n                        <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                      </elementProp>\n                      <elementProp name=\"sesskey\" elementType=\"HTTPArgument\">\n                        <boolProp name=\"HTTPArgument.always_encode\">false</boolProp>\n                        <stringProp name=\"Argument.value\">${sesskey}</stringProp>\n                        <stringProp name=\"Argument.metadata\">=</stringProp>\n                        <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                        <stringProp name=\"Argument.name\">sesskey</stringProp>\n                      </elementProp>\n                    </collectionProp>\n                  </elementProp>\n                  <stringProp name=\"HTTPSampler.domain\">${host}</stringProp>\n                  <stringProp name=\"HTTPSampler.port\">${port}</stringProp>\n                  <stringProp name=\"HTTPSampler.protocol\">${protocol}</stringProp>\n                  <stringProp name=\"HTTPSampler.contentEncoding\"></stringProp>\n                  <stringProp name=\"HTTPSampler.path\">/mod/quiz/startattempt.php</stringProp>\n                  <stringProp name=\"HTTPSampler.method\">POST</stringProp>\n                  <boolProp name=\"HTTPSampler.follow_redirects\">true</boolProp>\n                  <boolProp name=\"HTTPSampler.auto_redirects\">false</boolProp>\n                  <boolProp name=\"HTTPSampler.use_keepalive\">true</boolProp>\n                  <boolProp name=\"HTTPSampler.DO_MULTIPART_POST\">false</boolProp>\n                  <stringProp name=\"HTTPSampler.embedded_url_re\"></stringProp>\n                  <stringProp name=\"HTTPSampler.implementation\">Java</stringProp>\n                  <stringProp name=\"HTTPSampler.connect_timeout\"></stringProp>\n                  <stringProp name=\"HTTPSampler.response_timeout\"></stringProp>\n                </HTTPSamplerProxy>\n                <hashTree>\n                  <ResponseAssertion guiclass=\"AssertionGui\" testclass=\"ResponseAssertion\" testname=\"Assertion - in the quiz ?\" enabled=\"true\">\n                    <collectionProp name=\"Asserion.test_strings\">\n                      <stringProp name=\"-1545612013\">What&apos;s your username</stringProp>\n                      <stringProp name=\"-2127233155\">Is the LMS Performing acceptably?</stringProp>\n                      <stringProp name=\"-1512513172\">What is 2 \\+ 2\\?</stringProp>\n                    </collectionProp>\n                    <stringProp name=\"Assertion.test_field\">Assertion.response_data</stringProp>\n                    <boolProp name=\"Assertion.assume_success\">false</boolProp>\n                    <intProp name=\"Assertion.test_type\">2</intProp>\n                    <stringProp name=\"Assertion.custom_message\"></stringProp>\n                  </ResponseAssertion>\n                  <hashTree/>\n                  <GaussianRandomTimer guiclass=\"GaussianRandomTimerGui\" testclass=\"GaussianRandomTimer\" testname=\"Gaussian Random Timer\" enabled=\"true\">\n                    <stringProp name=\"ConstantTimer.delay\">10000</stringProp>\n                    <stringProp name=\"RandomTimer.range\">3000</stringProp>\n                  </GaussianRandomTimer>\n                  <hashTree/>\n                  <ConstantTimer guiclass=\"ConstantTimerGui\" testclass=\"ConstantTimer\" testname=\"Constant Timer\" enabled=\"false\">\n                    <stringProp name=\"ConstantTimer.delay\">1000</stringProp>\n                  </ConstantTimer>\n                  <hashTree/>\n                  <XPathExtractor guiclass=\"XPathExtractorGui\" testclass=\"XPathExtractor\" testname=\"Get attemptid\" enabled=\"false\">\n                    <stringProp name=\"XPathExtractor.default\"></stringProp>\n                    <stringProp name=\"XPathExtractor.refname\">attemptid</stringProp>\n                    <stringProp name=\"XPathExtractor.xpathQuery\">//*/form[@action=&quot;https://${host}/mod/quiz/processattempt.php&quot;]//*/input[@name=&apos;attempt&apos;][1]/@value</stringProp>\n                    <boolProp name=\"XPathExtractor.validate\">false</boolProp>\n                    <boolProp name=\"XPathExtractor.tolerant\">true</boolProp>\n                    <boolProp name=\"XPathExtractor.namespace\">false</boolProp>\n                    <boolProp name=\"XPathExtractor.show_warnings\">true</boolProp>\n                    <boolProp name=\"XPathExtractor.report_errors\">true</boolProp>\n                    <boolProp name=\"XPathExtractor.quiet\">false</boolProp>\n                    <stringProp name=\"XPathExtractor.matchNumber\">-1</stringProp>\n                  </XPathExtractor>\n                  <hashTree/>\n                  <RegexExtractor guiclass=\"RegexExtractorGui\" testclass=\"RegexExtractor\" testname=\"RegExp - Get attemptip\" enabled=\"true\">\n                    <stringProp name=\"RegexExtractor.useHeaders\">false</stringProp>\n                    <stringProp name=\"RegexExtractor.refname\">attemptid</stringProp>\n                    <stringProp name=\"RegexExtractor.regex\">\\/mod\\/quiz\\/attempt\\.php\\?attempt=(\\d+)&quot;&gt;Continue&lt;\\/a&gt;</stringProp>\n                    <stringProp name=\"RegexExtractor.template\">$1$</stringProp>\n                    <stringProp name=\"RegexExtractor.default\">NULL</stringProp>\n                    <stringProp name=\"RegexExtractor.match_number\"></stringProp>\n                    <stringProp name=\"Sample.scope\">children</stringProp>\n                  </RegexExtractor>\n                  <hashTree/>\n                  <ResultCollector guiclass=\"ViewResultsFullVisualizer\" testclass=\"ResultCollector\" testname=\"View Results Tree\" enabled=\"false\">\n                    <boolProp name=\"ResultCollector.error_logging\">false</boolProp>\n                    <objProp>\n                      <name>saveConfig</name>\n                      <value class=\"SampleSaveConfiguration\">\n                        <time>true</time>\n                        <latency>true</latency>\n                        <timestamp>true</timestamp>\n                        <success>true</success>\n                        <label>true</label>\n                        <code>true</code>\n                        <message>true</message>\n                        <threadName>true</threadName>\n                        <dataType>true</dataType>\n                        <encoding>false</encoding>\n                        <assertions>true</assertions>\n                        <subresults>true</subresults>\n                        <responseData>false</responseData>\n                        <samplerData>false</samplerData>\n                        <xml>false</xml>\n                        <fieldNames>true</fieldNames>\n                        <responseHeaders>false</responseHeaders>\n                        <requestHeaders>false</requestHeaders>\n                        <responseDataOnError>false</responseDataOnError>\n                        <saveAssertionResultsFailureMessage>true</saveAssertionResultsFailureMessage>\n                        <assertionsResultsToSave>0</assertionsResultsToSave>\n                        <bytes>true</bytes>\n                        <sentBytes>true</sentBytes>\n                        <threadCounts>true</threadCounts>\n                        <idleTime>true</idleTime>\n                        <connectTime>true</connectTime>\n                      </value>\n                    </objProp>\n                    <stringProp name=\"filename\"></stringProp>\n                  </ResultCollector>\n                  <hashTree/>\n                </hashTree>\n                <HTTPSamplerProxy guiclass=\"HttpTestSampleGui\" testclass=\"HTTPSamplerProxy\" testname=\"Submit the quiz attempt\" enabled=\"true\">\n                  <elementProp name=\"HTTPsampler.Arguments\" elementType=\"Arguments\" guiclass=\"HTTPArgumentsPanel\" testclass=\"Arguments\" testname=\"Argument List\" enabled=\"true\">\n                    <collectionProp name=\"Arguments.arguments\">\n                      <elementProp name=\"q${attemptid}:1_:flagged\" elementType=\"HTTPArgument\">\n                        <boolProp name=\"HTTPArgument.always_encode\">false</boolProp>\n                        <stringProp name=\"Argument.value\">0</stringProp>\n                        <stringProp name=\"Argument.metadata\">=</stringProp>\n                        <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                        <stringProp name=\"Argument.name\">q${attemptid}:1_:flagged</stringProp>\n                      </elementProp>\n                      <elementProp name=\"q${attemptid}:1_:sequencecheck\" elementType=\"HTTPArgument\">\n                        <boolProp name=\"HTTPArgument.always_encode\">false</boolProp>\n                        <stringProp name=\"Argument.value\">1</stringProp>\n                        <stringProp name=\"Argument.metadata\">=</stringProp>\n                        <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                        <stringProp name=\"Argument.name\">q${attemptid}:1_:sequencecheck</stringProp>\n                      </elementProp>\n                      <elementProp name=\"q${attemptid}:1_answer\" elementType=\"HTTPArgument\">\n                        <boolProp name=\"HTTPArgument.always_encode\">false</boolProp>\n                        <stringProp name=\"Argument.value\">${username}@${user_type}</stringProp>\n                        <stringProp name=\"Argument.metadata\">=</stringProp>\n                        <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                        <stringProp name=\"Argument.name\">q${attemptid}:1_answer</stringProp>\n                      </elementProp>\n                      <elementProp name=\"q${attemptid}:2_:flagged\" elementType=\"HTTPArgument\">\n                        <boolProp name=\"HTTPArgument.always_encode\">false</boolProp>\n                        <stringProp name=\"Argument.value\">0</stringProp>\n                        <stringProp name=\"Argument.metadata\">=</stringProp>\n                        <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                        <stringProp name=\"Argument.name\">q${attemptid}:2_:flagged</stringProp>\n                      </elementProp>\n                      <elementProp name=\"q${attemptid}:2_:sequencecheck\" elementType=\"HTTPArgument\">\n                        <boolProp name=\"HTTPArgument.always_encode\">false</boolProp>\n                        <stringProp name=\"Argument.value\">1</stringProp>\n                        <stringProp name=\"Argument.metadata\">=</stringProp>\n                        <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                        <stringProp name=\"Argument.name\">q${attemptid}:2_:sequencecheck</stringProp>\n                      </elementProp>\n                      <elementProp name=\"q${attemptid}:2_answer\" elementType=\"HTTPArgument\">\n                        <boolProp name=\"HTTPArgument.always_encode\">false</boolProp>\n                        <stringProp name=\"Argument.value\">${__Random(0,1)}</stringProp>\n                        <stringProp name=\"Argument.metadata\">=</stringProp>\n                        <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                        <stringProp name=\"Argument.name\">q${attemptid}:2_answer</stringProp>\n                      </elementProp>\n                      <elementProp name=\"q${attemptid}:3_:flagged\" elementType=\"HTTPArgument\">\n                        <boolProp name=\"HTTPArgument.always_encode\">false</boolProp>\n                        <stringProp name=\"Argument.value\">0</stringProp>\n                        <stringProp name=\"Argument.metadata\">=</stringProp>\n                        <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                        <stringProp name=\"Argument.name\">q${attemptid}:3_:flagged</stringProp>\n                      </elementProp>\n                      <elementProp name=\"q${attemptid}:3_:sequencecheck\" elementType=\"HTTPArgument\">\n                        <boolProp name=\"HTTPArgument.always_encode\">false</boolProp>\n                        <stringProp name=\"Argument.value\">1</stringProp>\n                        <stringProp name=\"Argument.metadata\">=</stringProp>\n                        <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                        <stringProp name=\"Argument.name\">q${attemptid}:3_:sequencecheck</stringProp>\n                      </elementProp>\n                      <elementProp name=\"q${attemptid}:3_answer\" elementType=\"HTTPArgument\">\n                        <boolProp name=\"HTTPArgument.always_encode\">false</boolProp>\n                        <stringProp name=\"Argument.value\">${__Random(3,5)}</stringProp>\n                        <stringProp name=\"Argument.metadata\">=</stringProp>\n                        <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                        <stringProp name=\"Argument.name\">q${attemptid}:3_answer</stringProp>\n                      </elementProp>\n                      <elementProp name=\"next\" elementType=\"HTTPArgument\">\n                        <boolProp name=\"HTTPArgument.always_encode\">false</boolProp>\n                        <stringProp name=\"Argument.value\">Next</stringProp>\n                        <stringProp name=\"Argument.metadata\">=</stringProp>\n                        <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                        <stringProp name=\"Argument.name\">next</stringProp>\n                      </elementProp>\n                      <elementProp name=\"attempt\" elementType=\"HTTPArgument\">\n                        <boolProp name=\"HTTPArgument.always_encode\">false</boolProp>\n                        <stringProp name=\"Argument.value\">${attemptid}</stringProp>\n                        <stringProp name=\"Argument.metadata\">=</stringProp>\n                        <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                        <stringProp name=\"Argument.name\">attempt</stringProp>\n                      </elementProp>\n                      <elementProp name=\"thispage\" elementType=\"HTTPArgument\">\n                        <boolProp name=\"HTTPArgument.always_encode\">false</boolProp>\n                        <stringProp name=\"Argument.value\">0</stringProp>\n                        <stringProp name=\"Argument.metadata\">=</stringProp>\n                        <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                        <stringProp name=\"Argument.name\">thispage</stringProp>\n                      </elementProp>\n                      <elementProp name=\"nextpage\" elementType=\"HTTPArgument\">\n                        <boolProp name=\"HTTPArgument.always_encode\">false</boolProp>\n                        <stringProp name=\"Argument.value\">-1</stringProp>\n                        <stringProp name=\"Argument.metadata\">=</stringProp>\n                        <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                        <stringProp name=\"Argument.name\">nextpage</stringProp>\n                      </elementProp>\n                      <elementProp name=\"timeup\" elementType=\"HTTPArgument\">\n                        <boolProp name=\"HTTPArgument.always_encode\">false</boolProp>\n                        <stringProp name=\"Argument.value\">0</stringProp>\n                        <stringProp name=\"Argument.metadata\">=</stringProp>\n                        <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                        <stringProp name=\"Argument.name\">timeup</stringProp>\n                      </elementProp>\n                      <elementProp name=\"sesskey\" elementType=\"HTTPArgument\">\n                        <boolProp name=\"HTTPArgument.always_encode\">false</boolProp>\n                        <stringProp name=\"Argument.value\">${sesskey}</stringProp>\n                        <stringProp name=\"Argument.metadata\">=</stringProp>\n                        <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                        <stringProp name=\"Argument.name\">sesskey</stringProp>\n                      </elementProp>\n                      <elementProp name=\"scrollpos\" elementType=\"HTTPArgument\">\n                        <boolProp name=\"HTTPArgument.always_encode\">false</boolProp>\n                        <stringProp name=\"Argument.value\"></stringProp>\n                        <stringProp name=\"Argument.metadata\">=</stringProp>\n                        <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                        <stringProp name=\"Argument.name\">scrollpos</stringProp>\n                      </elementProp>\n                      <elementProp name=\"slots\" elementType=\"HTTPArgument\">\n                        <boolProp name=\"HTTPArgument.always_encode\">false</boolProp>\n                        <stringProp name=\"Argument.value\">1,2,3</stringProp>\n                        <stringProp name=\"Argument.metadata\">=</stringProp>\n                        <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                        <stringProp name=\"Argument.name\">slots</stringProp>\n                      </elementProp>\n                    </collectionProp>\n                  </elementProp>\n                  <stringProp name=\"HTTPSampler.domain\">${host}</stringProp>\n                  <stringProp name=\"HTTPSampler.port\">${port}</stringProp>\n                  <stringProp name=\"HTTPSampler.protocol\">${protocol}</stringProp>\n                  <stringProp name=\"HTTPSampler.contentEncoding\">utf-8</stringProp>\n                  <stringProp name=\"HTTPSampler.path\">/mod/quiz/processattempt.php</stringProp>\n                  <stringProp name=\"HTTPSampler.method\">POST</stringProp>\n                  <boolProp name=\"HTTPSampler.follow_redirects\">true</boolProp>\n                  <boolProp name=\"HTTPSampler.auto_redirects\">false</boolProp>\n                  <boolProp name=\"HTTPSampler.use_keepalive\">true</boolProp>\n                  <boolProp name=\"HTTPSampler.DO_MULTIPART_POST\">true</boolProp>\n                  <stringProp name=\"HTTPSampler.embedded_url_re\"></stringProp>\n                  <stringProp name=\"HTTPSampler.implementation\">Java</stringProp>\n                  <stringProp name=\"HTTPSampler.connect_timeout\"></stringProp>\n                  <stringProp name=\"HTTPSampler.response_timeout\"></stringProp>\n                </HTTPSamplerProxy>\n                <hashTree>\n                  <ResponseAssertion guiclass=\"AssertionGui\" testclass=\"ResponseAssertion\" testname=\"Assertion - Response Contains Review of attempt 1\" enabled=\"false\">\n                    <collectionProp name=\"Asserion.test_strings\">\n                      <stringProp name=\"15650604\">Review of attempt</stringProp>\n                    </collectionProp>\n                    <intProp name=\"Assertion.test_type\">2</intProp>\n                    <stringProp name=\"Assertion.test_field\">Assertion.response_data</stringProp>\n                    <boolProp name=\"Assertion.assume_success\">false</boolProp>\n                    <stringProp name=\"Assertion.custom_message\"></stringProp>\n                  </ResponseAssertion>\n                  <hashTree/>\n                  <GaussianRandomTimer guiclass=\"GaussianRandomTimerGui\" testclass=\"GaussianRandomTimer\" testname=\"Gaussian Random Timer\" enabled=\"true\">\n                    <stringProp name=\"ConstantTimer.delay\">25000</stringProp>\n                    <stringProp name=\"RandomTimer.range\">15000</stringProp>\n                  </GaussianRandomTimer>\n                  <hashTree/>\n                  <ConstantTimer guiclass=\"ConstantTimerGui\" testclass=\"ConstantTimer\" testname=\"Constant Timer\" enabled=\"false\">\n                    <stringProp name=\"ConstantTimer.delay\">1000</stringProp>\n                  </ConstantTimer>\n                  <hashTree/>\n                  <ResultCollector guiclass=\"ViewResultsFullVisualizer\" testclass=\"ResultCollector\" testname=\"View Results Tree\" enabled=\"true\">\n                    <boolProp name=\"ResultCollector.error_logging\">false</boolProp>\n                    <objProp>\n                      <name>saveConfig</name>\n                      <value class=\"SampleSaveConfiguration\">\n                        <time>true</time>\n                        <latency>true</latency>\n                        <timestamp>true</timestamp>\n                        <success>true</success>\n                        <label>true</label>\n                        <code>true</code>\n                        <message>true</message>\n                        <threadName>true</threadName>\n                        <dataType>true</dataType>\n                        <encoding>false</encoding>\n                        <assertions>true</assertions>\n                        <subresults>true</subresults>\n                        <responseData>false</responseData>\n                        <samplerData>false</samplerData>\n                        <xml>false</xml>\n                        <fieldNames>true</fieldNames>\n                        <responseHeaders>false</responseHeaders>\n                        <requestHeaders>false</requestHeaders>\n                        <responseDataOnError>false</responseDataOnError>\n                        <saveAssertionResultsFailureMessage>true</saveAssertionResultsFailureMessage>\n                        <assertionsResultsToSave>0</assertionsResultsToSave>\n                        <bytes>true</bytes>\n                        <sentBytes>true</sentBytes>\n                        <threadCounts>true</threadCounts>\n                        <idleTime>true</idleTime>\n                        <connectTime>true</connectTime>\n                      </value>\n                    </objProp>\n                    <stringProp name=\"filename\"></stringProp>\n                  </ResultCollector>\n                  <hashTree/>\n                </hashTree>\n                <HTTPSamplerProxy guiclass=\"HttpTestSampleGui\" testclass=\"HTTPSamplerProxy\" testname=\"Submit all and finish\" enabled=\"true\">\n                  <elementProp name=\"HTTPsampler.Arguments\" elementType=\"Arguments\" guiclass=\"HTTPArgumentsPanel\" testclass=\"Arguments\" testname=\"Argument List\" enabled=\"true\">\n                    <collectionProp name=\"Arguments.arguments\">\n                      <elementProp name=\"sesskey\" elementType=\"HTTPArgument\">\n                        <boolProp name=\"HTTPArgument.always_encode\">false</boolProp>\n                        <stringProp name=\"Argument.value\">${sesskey}</stringProp>\n                        <stringProp name=\"Argument.metadata\">=</stringProp>\n                        <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                        <stringProp name=\"Argument.name\">sesskey</stringProp>\n                      </elementProp>\n                      <elementProp name=\"slots\" elementType=\"HTTPArgument\">\n                        <boolProp name=\"HTTPArgument.always_encode\">false</boolProp>\n                        <stringProp name=\"Argument.value\"></stringProp>\n                        <stringProp name=\"Argument.metadata\">=</stringProp>\n                        <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                        <stringProp name=\"Argument.name\">slots</stringProp>\n                      </elementProp>\n                      <elementProp name=\"timeup\" elementType=\"HTTPArgument\">\n                        <boolProp name=\"HTTPArgument.always_encode\">false</boolProp>\n                        <stringProp name=\"Argument.value\">0</stringProp>\n                        <stringProp name=\"Argument.metadata\">=</stringProp>\n                        <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                        <stringProp name=\"Argument.name\">timeup</stringProp>\n                      </elementProp>\n                      <elementProp name=\"finishattempt\" elementType=\"HTTPArgument\">\n                        <boolProp name=\"HTTPArgument.always_encode\">false</boolProp>\n                        <stringProp name=\"Argument.value\">1</stringProp>\n                        <stringProp name=\"Argument.metadata\">=</stringProp>\n                        <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                        <stringProp name=\"Argument.name\">finishattempt</stringProp>\n                      </elementProp>\n                      <elementProp name=\"attempt\" elementType=\"HTTPArgument\">\n                        <boolProp name=\"HTTPArgument.always_encode\">false</boolProp>\n                        <stringProp name=\"Argument.value\">${attemptid}</stringProp>\n                        <stringProp name=\"Argument.metadata\">=</stringProp>\n                        <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                        <stringProp name=\"Argument.name\">attempt</stringProp>\n                      </elementProp>\n                    </collectionProp>\n                  </elementProp>\n                  <stringProp name=\"HTTPSampler.domain\">${host}</stringProp>\n                  <stringProp name=\"HTTPSampler.port\">${port}</stringProp>\n                  <stringProp name=\"HTTPSampler.protocol\">${protocol}</stringProp>\n                  <stringProp name=\"HTTPSampler.contentEncoding\">utf-8</stringProp>\n                  <stringProp name=\"HTTPSampler.path\">/mod/quiz/processattempt.php</stringProp>\n                  <stringProp name=\"HTTPSampler.method\">POST</stringProp>\n                  <boolProp name=\"HTTPSampler.follow_redirects\">true</boolProp>\n                  <boolProp name=\"HTTPSampler.auto_redirects\">false</boolProp>\n                  <boolProp name=\"HTTPSampler.use_keepalive\">true</boolProp>\n                  <boolProp name=\"HTTPSampler.DO_MULTIPART_POST\">true</boolProp>\n                  <stringProp name=\"HTTPSampler.embedded_url_re\"></stringProp>\n                  <stringProp name=\"HTTPSampler.implementation\">Java</stringProp>\n                  <stringProp name=\"HTTPSampler.connect_timeout\"></stringProp>\n                  <stringProp name=\"HTTPSampler.response_timeout\"></stringProp>\n                </HTTPSamplerProxy>\n                <hashTree>\n                  <ResponseAssertion guiclass=\"AssertionGui\" testclass=\"ResponseAssertion\" testname=\"Assertion - Response Contains Review of attempt 1\" enabled=\"false\">\n                    <collectionProp name=\"Asserion.test_strings\">\n                      <stringProp name=\"15650604\">Review of attempt</stringProp>\n                    </collectionProp>\n                    <intProp name=\"Assertion.test_type\">2</intProp>\n                    <stringProp name=\"Assertion.test_field\">Assertion.response_data</stringProp>\n                    <boolProp name=\"Assertion.assume_success\">false</boolProp>\n                    <stringProp name=\"Assertion.custom_message\"></stringProp>\n                  </ResponseAssertion>\n                  <hashTree/>\n                  <GaussianRandomTimer guiclass=\"GaussianRandomTimerGui\" testclass=\"GaussianRandomTimer\" testname=\"Gaussian Random Timer\" enabled=\"true\">\n                    <stringProp name=\"ConstantTimer.delay\">4000</stringProp>\n                    <stringProp name=\"RandomTimer.range\">3000</stringProp>\n                  </GaussianRandomTimer>\n                  <hashTree/>\n                  <ConstantTimer guiclass=\"ConstantTimerGui\" testclass=\"ConstantTimer\" testname=\"Constant Timer\" enabled=\"false\">\n                    <stringProp name=\"ConstantTimer.delay\">1000</stringProp>\n                  </ConstantTimer>\n                  <hashTree/>\n                  <ResultCollector guiclass=\"ViewResultsFullVisualizer\" testclass=\"ResultCollector\" testname=\"View Results Tree\" enabled=\"false\">\n                    <boolProp name=\"ResultCollector.error_logging\">false</boolProp>\n                    <objProp>\n                      <name>saveConfig</name>\n                      <value class=\"SampleSaveConfiguration\">\n                        <time>true</time>\n                        <latency>true</latency>\n                        <timestamp>true</timestamp>\n                        <success>true</success>\n                        <label>true</label>\n                        <code>true</code>\n                        <message>true</message>\n                        <threadName>true</threadName>\n                        <dataType>true</dataType>\n                        <encoding>false</encoding>\n                        <assertions>true</assertions>\n                        <subresults>true</subresults>\n                        <responseData>false</responseData>\n                        <samplerData>false</samplerData>\n                        <xml>false</xml>\n                        <fieldNames>true</fieldNames>\n                        <responseHeaders>false</responseHeaders>\n                        <requestHeaders>false</requestHeaders>\n                        <responseDataOnError>false</responseDataOnError>\n                        <saveAssertionResultsFailureMessage>true</saveAssertionResultsFailureMessage>\n                        <assertionsResultsToSave>0</assertionsResultsToSave>\n                        <bytes>true</bytes>\n                        <sentBytes>true</sentBytes>\n                        <threadCounts>true</threadCounts>\n                        <idleTime>true</idleTime>\n                        <connectTime>true</connectTime>\n                      </value>\n                    </objProp>\n                    <stringProp name=\"filename\"></stringProp>\n                  </ResultCollector>\n                  <hashTree/>\n                </hashTree>\n                <HTTPSamplerProxy guiclass=\"HttpTestSampleGui\" testclass=\"HTTPSamplerProxy\" testname=\"Click Finish Review\" enabled=\"true\">\n                  <elementProp name=\"HTTPsampler.Arguments\" elementType=\"Arguments\" guiclass=\"HTTPArgumentsPanel\" testclass=\"Arguments\" testname=\"Argument List\" enabled=\"true\">\n                    <collectionProp name=\"Arguments.arguments\">\n                      <elementProp name=\"id\" elementType=\"HTTPArgument\">\n                        <boolProp name=\"HTTPArgument.always_encode\">false</boolProp>\n                        <stringProp name=\"Argument.value\">${moduleid}</stringProp>\n                        <stringProp name=\"Argument.metadata\">=</stringProp>\n                        <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                        <stringProp name=\"Argument.name\">id</stringProp>\n                      </elementProp>\n                    </collectionProp>\n                  </elementProp>\n                  <stringProp name=\"HTTPSampler.domain\">${host}</stringProp>\n                  <stringProp name=\"HTTPSampler.port\">${port}</stringProp>\n                  <stringProp name=\"HTTPSampler.protocol\">${protocol}</stringProp>\n                  <stringProp name=\"HTTPSampler.contentEncoding\"></stringProp>\n                  <stringProp name=\"HTTPSampler.path\">/mod/quiz/view.php</stringProp>\n                  <stringProp name=\"HTTPSampler.method\">GET</stringProp>\n                  <boolProp name=\"HTTPSampler.follow_redirects\">true</boolProp>\n                  <boolProp name=\"HTTPSampler.auto_redirects\">false</boolProp>\n                  <boolProp name=\"HTTPSampler.use_keepalive\">true</boolProp>\n                  <boolProp name=\"HTTPSampler.DO_MULTIPART_POST\">true</boolProp>\n                  <stringProp name=\"HTTPSampler.embedded_url_re\"></stringProp>\n                  <stringProp name=\"HTTPSampler.implementation\">Java</stringProp>\n                  <stringProp name=\"HTTPSampler.connect_timeout\"></stringProp>\n                  <stringProp name=\"HTTPSampler.response_timeout\"></stringProp>\n                </HTTPSamplerProxy>\n                <hashTree>\n                  <ResponseAssertion guiclass=\"AssertionGui\" testclass=\"ResponseAssertion\" testname=\"Assertion - Response Contains Summary of your previous atte...\" enabled=\"false\">\n                    <collectionProp name=\"Asserion.test_strings\">\n                      <stringProp name=\"1813422065\">Summary of your previous attempts</stringProp>\n                    </collectionProp>\n                    <intProp name=\"Assertion.test_type\">2</intProp>\n                    <stringProp name=\"Assertion.test_field\">Assertion.response_data</stringProp>\n                    <boolProp name=\"Assertion.assume_success\">false</boolProp>\n                    <stringProp name=\"Assertion.custom_message\"></stringProp>\n                  </ResponseAssertion>\n                  <hashTree/>\n                  <ResponseAssertion guiclass=\"AssertionGui\" testclass=\"ResponseAssertion\" testname=\"Assertion - Response Contains Highest grade\" enabled=\"false\">\n                    <collectionProp name=\"Asserion.test_strings\">\n                      <stringProp name=\"-1064949445\">Highest grade</stringProp>\n                    </collectionProp>\n                    <intProp name=\"Assertion.test_type\">2</intProp>\n                    <stringProp name=\"Assertion.test_field\">Assertion.response_data</stringProp>\n                    <boolProp name=\"Assertion.assume_success\">false</boolProp>\n                    <stringProp name=\"Assertion.custom_message\"></stringProp>\n                  </ResponseAssertion>\n                  <hashTree/>\n                  <GaussianRandomTimer guiclass=\"GaussianRandomTimerGui\" testclass=\"GaussianRandomTimer\" testname=\"Gaussian Random Timer\" enabled=\"true\">\n                    <stringProp name=\"ConstantTimer.delay\">4000</stringProp>\n                    <stringProp name=\"RandomTimer.range\">3000</stringProp>\n                  </GaussianRandomTimer>\n                  <hashTree/>\n                  <ConstantTimer guiclass=\"ConstantTimerGui\" testclass=\"ConstantTimer\" testname=\"Constant Timer\" enabled=\"false\">\n                    <stringProp name=\"ConstantTimer.delay\">1000</stringProp>\n                  </ConstantTimer>\n                  <hashTree/>\n                </hashTree>\n                <DebugSampler guiclass=\"TestBeanGUI\" testclass=\"DebugSampler\" testname=\"Debug Sampler\" enabled=\"false\">\n                  <boolProp name=\"displayJMeterProperties\">false</boolProp>\n                  <boolProp name=\"displayJMeterVariables\">true</boolProp>\n                  <boolProp name=\"displaySystemProperties\">false</boolProp>\n                </DebugSampler>\n                <hashTree/>\n                <ResultCollector guiclass=\"GraphVisualizer\" testclass=\"ResultCollector\" testname=\"Graph Results\" enabled=\"false\">\n                  <boolProp name=\"ResultCollector.error_logging\">false</boolProp>\n                  <objProp>\n                    <name>saveConfig</name>\n                    <value class=\"SampleSaveConfiguration\">\n                      <time>true</time>\n                      <latency>true</latency>\n                      <timestamp>true</timestamp>\n                      <success>true</success>\n                      <label>true</label>\n                      <code>true</code>\n                      <message>true</message>\n                      <threadName>true</threadName>\n                      <dataType>true</dataType>\n                      <encoding>false</encoding>\n                      <assertions>true</assertions>\n                      <subresults>true</subresults>\n                      <responseData>false</responseData>\n                      <samplerData>false</samplerData>\n                      <xml>true</xml>\n                      <fieldNames>false</fieldNames>\n                      <responseHeaders>false</responseHeaders>\n                      <requestHeaders>false</requestHeaders>\n                      <responseDataOnError>false</responseDataOnError>\n                      <saveAssertionResultsFailureMessage>false</saveAssertionResultsFailureMessage>\n                      <assertionsResultsToSave>0</assertionsResultsToSave>\n                      <bytes>true</bytes>\n                      <threadCounts>true</threadCounts>\n                    </value>\n                  </objProp>\n                  <stringProp name=\"filename\"></stringProp>\n                </ResultCollector>\n                <hashTree/>\n                <ResultCollector guiclass=\"ViewResultsFullVisualizer\" testclass=\"ResultCollector\" testname=\"View Results Tree\" enabled=\"false\">\n                  <boolProp name=\"ResultCollector.error_logging\">false</boolProp>\n                  <objProp>\n                    <name>saveConfig</name>\n                    <value class=\"SampleSaveConfiguration\">\n                      <time>true</time>\n                      <latency>true</latency>\n                      <timestamp>true</timestamp>\n                      <success>true</success>\n                      <label>true</label>\n                      <code>true</code>\n                      <message>true</message>\n                      <threadName>true</threadName>\n                      <dataType>true</dataType>\n                      <encoding>false</encoding>\n                      <assertions>true</assertions>\n                      <subresults>true</subresults>\n                      <responseData>false</responseData>\n                      <samplerData>false</samplerData>\n                      <xml>false</xml>\n                      <fieldNames>false</fieldNames>\n                      <responseHeaders>false</responseHeaders>\n                      <requestHeaders>false</requestHeaders>\n                      <responseDataOnError>false</responseDataOnError>\n                      <saveAssertionResultsFailureMessage>false</saveAssertionResultsFailureMessage>\n                      <assertionsResultsToSave>0</assertionsResultsToSave>\n                      <bytes>true</bytes>\n                      <threadCounts>true</threadCounts>\n                    </value>\n                  </objProp>\n                  <stringProp name=\"filename\"></stringProp>\n                </ResultCollector>\n                <hashTree/>\n                <GaussianRandomTimer guiclass=\"GaussianRandomTimerGui\" testclass=\"GaussianRandomTimer\" testname=\"Gaussian Random Timer\" enabled=\"true\">\n                  <stringProp name=\"ConstantTimer.delay\">20000</stringProp>\n                  <stringProp name=\"RandomTimer.range\">20000</stringProp>\n                </GaussianRandomTimer>\n                <hashTree/>\n              </hashTree>\n            </hashTree>\n          </hashTree>\n          <ResultCollector guiclass=\"TableVisualizer\" testclass=\"ResultCollector\" testname=\"View Results in Table\" enabled=\"true\">\n            <boolProp name=\"ResultCollector.error_logging\">false</boolProp>\n            <objProp>\n              <name>saveConfig</name>\n              <value class=\"SampleSaveConfiguration\">\n                <time>true</time>\n                <latency>true</latency>\n                <timestamp>true</timestamp>\n                <success>true</success>\n                <label>true</label>\n                <code>true</code>\n                <message>true</message>\n                <threadName>true</threadName>\n                <dataType>true</dataType>\n                <encoding>false</encoding>\n                <assertions>true</assertions>\n                <subresults>true</subresults>\n                <responseData>false</responseData>\n                <samplerData>false</samplerData>\n                <xml>false</xml>\n                <fieldNames>true</fieldNames>\n                <responseHeaders>false</responseHeaders>\n                <requestHeaders>false</requestHeaders>\n                <responseDataOnError>false</responseDataOnError>\n                <saveAssertionResultsFailureMessage>true</saveAssertionResultsFailureMessage>\n                <assertionsResultsToSave>0</assertionsResultsToSave>\n                <bytes>true</bytes>\n                <sentBytes>true</sentBytes>\n                <threadCounts>true</threadCounts>\n                <idleTime>true</idleTime>\n                <connectTime>true</connectTime>\n              </value>\n            </objProp>\n            <stringProp name=\"filename\"></stringProp>\n          </ResultCollector>\n          <hashTree/>\n          <ResultCollector guiclass=\"SummaryReport\" testclass=\"ResultCollector\" testname=\"Summary Report\" enabled=\"true\">\n            <boolProp name=\"ResultCollector.error_logging\">false</boolProp>\n            <objProp>\n              <name>saveConfig</name>\n              <value class=\"SampleSaveConfiguration\">\n                <time>true</time>\n                <latency>true</latency>\n                <timestamp>true</timestamp>\n                <success>true</success>\n                <label>true</label>\n                <code>true</code>\n                <message>true</message>\n                <threadName>true</threadName>\n                <dataType>true</dataType>\n                <encoding>false</encoding>\n                <assertions>true</assertions>\n                <subresults>true</subresults>\n                <responseData>false</responseData>\n                <samplerData>false</samplerData>\n                <xml>false</xml>\n                <fieldNames>true</fieldNames>\n                <responseHeaders>false</responseHeaders>\n                <requestHeaders>false</requestHeaders>\n                <responseDataOnError>false</responseDataOnError>\n                <saveAssertionResultsFailureMessage>true</saveAssertionResultsFailureMessage>\n                <assertionsResultsToSave>0</assertionsResultsToSave>\n                <bytes>true</bytes>\n                <sentBytes>true</sentBytes>\n                <threadCounts>true</threadCounts>\n                <idleTime>true</idleTime>\n                <connectTime>true</connectTime>\n              </value>\n            </objProp>\n            <stringProp name=\"filename\"></stringProp>\n          </ResultCollector>\n          <hashTree/>\n          <kg.apc.jmeter.vizualizers.CorrectedResultCollector guiclass=\"kg.apc.jmeter.vizualizers.HitsPerSecondGui\" testclass=\"kg.apc.jmeter.vizualizers.CorrectedResultCollector\" testname=\"jp@gc - Hits per Second\" enabled=\"true\">\n            <boolProp name=\"ResultCollector.error_logging\">false</boolProp>\n            <objProp>\n              <name>saveConfig</name>\n              <value class=\"SampleSaveConfiguration\">\n                <time>true</time>\n                <latency>true</latency>\n                <timestamp>true</timestamp>\n                <success>true</success>\n                <label>true</label>\n                <code>true</code>\n                <message>true</message>\n                <threadName>true</threadName>\n                <dataType>true</dataType>\n                <encoding>false</encoding>\n                <assertions>true</assertions>\n                <subresults>true</subresults>\n                <responseData>false</responseData>\n                <samplerData>false</samplerData>\n                <xml>false</xml>\n                <fieldNames>true</fieldNames>\n                <responseHeaders>false</responseHeaders>\n                <requestHeaders>false</requestHeaders>\n                <responseDataOnError>false</responseDataOnError>\n                <saveAssertionResultsFailureMessage>true</saveAssertionResultsFailureMessage>\n                <assertionsResultsToSave>0</assertionsResultsToSave>\n                <bytes>true</bytes>\n                <sentBytes>true</sentBytes>\n                <threadCounts>true</threadCounts>\n                <idleTime>true</idleTime>\n                <connectTime>true</connectTime>\n              </value>\n            </objProp>\n            <stringProp name=\"filename\"></stringProp>\n            <longProp name=\"interval_grouping\">1000</longProp>\n            <boolProp name=\"graph_aggregated\">false</boolProp>\n            <stringProp name=\"include_sample_labels\"></stringProp>\n            <stringProp name=\"exclude_sample_labels\"></stringProp>\n            <stringProp name=\"start_offset\"></stringProp>\n            <stringProp name=\"end_offset\"></stringProp>\n            <boolProp name=\"include_checkbox_state\">false</boolProp>\n            <boolProp name=\"exclude_checkbox_state\">false</boolProp>\n          </kg.apc.jmeter.vizualizers.CorrectedResultCollector>\n          <hashTree/>\n          <kg.apc.jmeter.vizualizers.CorrectedResultCollector guiclass=\"kg.apc.jmeter.vizualizers.ResponseCodesPerSecondGui\" testclass=\"kg.apc.jmeter.vizualizers.CorrectedResultCollector\" testname=\"jp@gc - Response Codes per Second\" enabled=\"true\">\n            <boolProp name=\"ResultCollector.error_logging\">false</boolProp>\n            <objProp>\n              <name>saveConfig</name>\n              <value class=\"SampleSaveConfiguration\">\n                <time>true</time>\n                <latency>true</latency>\n                <timestamp>true</timestamp>\n                <success>true</success>\n                <label>true</label>\n                <code>true</code>\n                <message>true</message>\n                <threadName>true</threadName>\n                <dataType>true</dataType>\n                <encoding>false</encoding>\n                <assertions>true</assertions>\n                <subresults>true</subresults>\n                <responseData>false</responseData>\n                <samplerData>false</samplerData>\n                <xml>false</xml>\n                <fieldNames>true</fieldNames>\n                <responseHeaders>false</responseHeaders>\n                <requestHeaders>false</requestHeaders>\n                <responseDataOnError>false</responseDataOnError>\n                <saveAssertionResultsFailureMessage>true</saveAssertionResultsFailureMessage>\n                <assertionsResultsToSave>0</assertionsResultsToSave>\n                <bytes>true</bytes>\n                <sentBytes>true</sentBytes>\n                <threadCounts>true</threadCounts>\n                <idleTime>true</idleTime>\n                <connectTime>true</connectTime>\n              </value>\n            </objProp>\n            <stringProp name=\"filename\"></stringProp>\n            <longProp name=\"interval_grouping\">1000</longProp>\n            <boolProp name=\"graph_aggregated\">false</boolProp>\n            <stringProp name=\"include_sample_labels\"></stringProp>\n            <stringProp name=\"exclude_sample_labels\"></stringProp>\n            <stringProp name=\"start_offset\"></stringProp>\n            <stringProp name=\"end_offset\"></stringProp>\n            <boolProp name=\"include_checkbox_state\">false</boolProp>\n            <boolProp name=\"exclude_checkbox_state\">false</boolProp>\n          </kg.apc.jmeter.vizualizers.CorrectedResultCollector>\n          <hashTree/>\n          <kg.apc.jmeter.vizualizers.CorrectedResultCollector guiclass=\"kg.apc.jmeter.vizualizers.LatenciesOverTimeGui\" testclass=\"kg.apc.jmeter.vizualizers.CorrectedResultCollector\" testname=\"jp@gc - Response Latencies Over Time\" enabled=\"true\">\n            <boolProp name=\"ResultCollector.error_logging\">false</boolProp>\n            <objProp>\n              <name>saveConfig</name>\n              <value class=\"SampleSaveConfiguration\">\n                <time>true</time>\n                <latency>true</latency>\n                <timestamp>true</timestamp>\n                <success>true</success>\n                <label>true</label>\n                <code>true</code>\n                <message>true</message>\n                <threadName>true</threadName>\n                <dataType>true</dataType>\n                <encoding>false</encoding>\n                <assertions>true</assertions>\n                <subresults>true</subresults>\n                <responseData>false</responseData>\n                <samplerData>false</samplerData>\n                <xml>false</xml>\n                <fieldNames>true</fieldNames>\n                <responseHeaders>false</responseHeaders>\n                <requestHeaders>false</requestHeaders>\n                <responseDataOnError>false</responseDataOnError>\n                <saveAssertionResultsFailureMessage>true</saveAssertionResultsFailureMessage>\n                <assertionsResultsToSave>0</assertionsResultsToSave>\n                <bytes>true</bytes>\n                <sentBytes>true</sentBytes>\n                <threadCounts>true</threadCounts>\n                <idleTime>true</idleTime>\n                <connectTime>true</connectTime>\n              </value>\n            </objProp>\n            <stringProp name=\"filename\"></stringProp>\n            <longProp name=\"interval_grouping\">500</longProp>\n            <boolProp name=\"graph_aggregated\">false</boolProp>\n            <stringProp name=\"include_sample_labels\"></stringProp>\n            <stringProp name=\"exclude_sample_labels\"></stringProp>\n            <stringProp name=\"start_offset\"></stringProp>\n            <stringProp name=\"end_offset\"></stringProp>\n            <boolProp name=\"include_checkbox_state\">false</boolProp>\n            <boolProp name=\"exclude_checkbox_state\">false</boolProp>\n          </kg.apc.jmeter.vizualizers.CorrectedResultCollector>\n          <hashTree/>\n        </hashTree>\n      </hashTree>\n    </hashTree>\n  </hashTree>\n</jmeterTestPlan>\n"
  },
  {
    "path": "loadtest/time-gated-exam-test-dist-slaves.jmx",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<jmeterTestPlan version=\"1.2\" properties=\"4.0\" jmeter=\"4.0 r1823414\">\n  <hashTree>\n    <TestPlan guiclass=\"TestPlanGui\" testclass=\"TestPlan\" testname=\"Moodle Load Test Plan\" enabled=\"true\">\n      <boolProp name=\"TestPlan.functional_mode\">false</boolProp>\n      <collectionProp name=\"TestPlan.thread_groups\"/>\n      <elementProp name=\"TestPlan.user_defined_variables\" elementType=\"Arguments\" guiclass=\"ArgumentsPanel\" testclass=\"Arguments\" testname=\"Argument List\" enabled=\"true\">\n        <collectionProp name=\"Arguments.arguments\"/>\n      </elementProp>\n      <boolProp name=\"TestPlan.serialize_threadgroups\">false</boolProp>\n      <stringProp name=\"TestPlan.comments\"></stringProp>\n      <stringProp name=\"TestPlan.user_define_classpath\"></stringProp>\n    </TestPlan>\n    <hashTree>\n      <Arguments guiclass=\"ArgumentsPanel\" testclass=\"Arguments\" testname=\"User Defined Variables\" enabled=\"true\">\n        <collectionProp name=\"Arguments.arguments\">\n          <elementProp name=\"login_host\" elementType=\"Argument\">\n            <stringProp name=\"Argument.name\">login_host</stringProp>\n            <stringProp name=\"Argument.value\">${__P(host, lb-kkvgra.southcentralus.cloudapp.azure.com)}</stringProp>\n            <stringProp name=\"Argument.metadata\">=</stringProp>\n          </elementProp>\n          <elementProp name=\"host\" elementType=\"Argument\">\n            <stringProp name=\"Argument.name\">host</stringProp>\n            <stringProp name=\"Argument.value\">${__P(host, lb-kkvgra.southcentralus.cloudapp.azure.com)}</stringProp>\n            <stringProp name=\"Argument.metadata\">=</stringProp>\n          </elementProp>\n          <elementProp name=\"threads\" elementType=\"Argument\">\n            <stringProp name=\"Argument.name\">threads</stringProp>\n            <stringProp name=\"Argument.value\">${__P(threads,250)}</stringProp>\n            <stringProp name=\"Argument.metadata\">=</stringProp>\n            <stringProp name=\"Argument.desc\">Default 10 (for a quick GUI run)</stringProp>\n          </elementProp>\n          <elementProp name=\"rampup\" elementType=\"Argument\">\n            <stringProp name=\"Argument.name\">rampup</stringProp>\n            <stringProp name=\"Argument.value\">${__P(rampup,250)}</stringProp>\n            <stringProp name=\"Argument.metadata\">=</stringProp>\n            <stringProp name=\"Argument.desc\">Default 120 - 1/15 runtime default rampup</stringProp>\n          </elementProp>\n          <elementProp name=\"runtime\" elementType=\"Argument\">\n            <stringProp name=\"Argument.name\">runtime</stringProp>\n            <stringProp name=\"Argument.value\">${__P(runtime,3600)}</stringProp>\n            <stringProp name=\"Argument.metadata\">=</stringProp>\n            <stringProp name=\"Argument.desc\">Default 1800 = 30 minutes = 30*60 seconds</stringProp>\n          </elementProp>\n          <elementProp name=\"default_connect_timeout\" elementType=\"Argument\">\n            <stringProp name=\"Argument.name\">default_connect_timeout</stringProp>\n            <stringProp name=\"Argument.value\">2000</stringProp>\n            <stringProp name=\"Argument.metadata\">=</stringProp>\n            <stringProp name=\"Argument.desc\">1.2 seconds to a TCP ack</stringProp>\n          </elementProp>\n          <elementProp name=\"default_response_timeout\" elementType=\"Argument\">\n            <stringProp name=\"Argument.name\">default_response_timeout</stringProp>\n            <stringProp name=\"Argument.value\">240000</stringProp>\n            <stringProp name=\"Argument.metadata\">=</stringProp>\n            <stringProp name=\"Argument.desc\">4 Minutes for the page to respond</stringProp>\n          </elementProp>\n          <elementProp name=\"throughput_rate\" elementType=\"Argument\">\n            <stringProp name=\"Argument.name\">throughput_rate</stringProp>\n            <stringProp name=\"Argument.value\">${__P(throughput_rate, 50)}</stringProp>\n            <stringProp name=\"Argument.metadata\">=</stringProp>\n            <stringProp name=\"Argument.desc\">Default 650 should approximate ~ 7k page views / 5 mins cacti sample</stringProp>\n          </elementProp>\n          <elementProp name=\"activity_course_id\" elementType=\"Argument\">\n            <stringProp name=\"Argument.name\">activity_course_id</stringProp>\n            <stringProp name=\"Argument.value\">${__P(activity_course_id, 2)}</stringProp>\n            <stringProp name=\"Argument.metadata\">=</stringProp>\n            <stringProp name=\"Argument.desc\">This must match the course ID of the course with performance activities</stringProp>\n          </elementProp>\n          <elementProp name=\"quiz_delay\" elementType=\"Argument\">\n            <stringProp name=\"Argument.name\">quiz_delay</stringProp>\n            <stringProp name=\"Argument.value\">${__P(quiz_delay, 100)}</stringProp>\n            <stringProp name=\"Argument.desc\">-1 means don&apos;t run quiz</stringProp>\n            <stringProp name=\"Argument.metadata\">=</stringProp>\n          </elementProp>\n          <elementProp name=\"quiz_users\" elementType=\"Argument\">\n            <stringProp name=\"Argument.name\">quiz_users</stringProp>\n            <stringProp name=\"Argument.value\">${__P(quiz_users, 1)}</stringProp>\n            <stringProp name=\"Argument.desc\">Number of quiz users to simulate</stringProp>\n            <stringProp name=\"Argument.metadata\">=</stringProp>\n          </elementProp>\n          <elementProp name=\"groupselect_delay\" elementType=\"Argument\">\n            <stringProp name=\"Argument.name\">groupselect_delay</stringProp>\n            <stringProp name=\"Argument.value\">${__P(groupselect_delay,-1)}</stringProp>\n            <stringProp name=\"Argument.metadata\">=</stringProp>\n          </elementProp>\n          <elementProp name=\"groupselect_users\" elementType=\"Argument\">\n            <stringProp name=\"Argument.name\">groupselect_users</stringProp>\n            <stringProp name=\"Argument.value\">${__P(groupselect_users, 0)}</stringProp>\n            <stringProp name=\"Argument.metadata\">=</stringProp>\n          </elementProp>\n          <elementProp name=\"protocol\" elementType=\"Argument\">\n            <stringProp name=\"Argument.name\">protocol</stringProp>\n            <stringProp name=\"Argument.value\">https</stringProp>\n            <stringProp name=\"Argument.metadata\">=</stringProp>\n          </elementProp>\n          <elementProp name=\"port\" elementType=\"Argument\">\n            <stringProp name=\"Argument.name\">port</stringProp>\n            <stringProp name=\"Argument.value\">443</stringProp>\n            <stringProp name=\"Argument.metadata\">=</stringProp>\n          </elementProp>\n          <elementProp name=\"moodle_user_pass\" elementType=\"Argument\">\n            <stringProp name=\"Argument.name\">moodle_user_pass</stringProp>\n            <stringProp name=\"Argument.value\">${__P(moodle_user_pass,testUserP@$$w0rd)}</stringProp>\n            <stringProp name=\"Argument.metadata\">=</stringProp>\n          </elementProp>\n        </collectionProp>\n      </Arguments>\n      <hashTree/>\n      <ThreadGroup guiclass=\"ThreadGroupGui\" testclass=\"ThreadGroup\" testname=\"Load Test Moodle\" enabled=\"true\">\n        <stringProp name=\"ThreadGroup.on_sample_error\">startnextloop</stringProp>\n        <elementProp name=\"ThreadGroup.main_controller\" elementType=\"LoopController\" guiclass=\"LoopControlPanel\" testclass=\"LoopController\" testname=\"Loop Controller\" enabled=\"true\">\n          <boolProp name=\"LoopController.continue_forever\">false</boolProp>\n          <stringProp name=\"LoopController.loops\">1</stringProp>\n        </elementProp>\n        <stringProp name=\"ThreadGroup.num_threads\">${threads}</stringProp>\n        <stringProp name=\"ThreadGroup.ramp_time\">${rampup}</stringProp>\n        <longProp name=\"ThreadGroup.start_time\">1513564238000</longProp>\n        <longProp name=\"ThreadGroup.end_time\">1513564538000</longProp>\n        <boolProp name=\"ThreadGroup.scheduler\">true</boolProp>\n        <stringProp name=\"ThreadGroup.duration\">${runtime}</stringProp>\n        <stringProp name=\"ThreadGroup.delay\">0</stringProp>\n      </ThreadGroup>\n      <hashTree>\n        <CookieManager guiclass=\"CookiePanel\" testclass=\"CookieManager\" testname=\"HTTP Cookie Manager\" enabled=\"true\">\n          <collectionProp name=\"CookieManager.cookies\"/>\n          <boolProp name=\"CookieManager.clearEachIteration\">true</boolProp>\n          <stringProp name=\"CookieManager.policy\">default</stringProp>\n        </CookieManager>\n        <hashTree/>\n        <GenericController guiclass=\"LogicControllerGui\" testclass=\"GenericController\" testname=\"Moodle Tests\" enabled=\"true\"/>\n        <hashTree>\n          <HeaderManager guiclass=\"HeaderPanel\" testclass=\"HeaderManager\" testname=\"HTTP Header Manager\" enabled=\"true\">\n            <collectionProp name=\"HeaderManager.headers\">\n              <elementProp name=\"\" elementType=\"Header\">\n                <stringProp name=\"Header.name\">User-Agent</stringProp>\n                <stringProp name=\"Header.value\">Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0; Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1) ; InfoPath.1; .NET CLR 1.1.4322; .NET CLR 2.0.50727; .NET CLR 3.0.04506.648; .NET CLR 3.5.21022; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729; .NET4.0C; OfficeLiveConnector.1.5; OfficeLivePatch.1.3)</stringProp>\n              </elementProp>\n              <elementProp name=\"\" elementType=\"Header\">\n                <stringProp name=\"Header.name\">Accept</stringProp>\n                <stringProp name=\"Header.value\">*/*</stringProp>\n              </elementProp>\n              <elementProp name=\"\" elementType=\"Header\">\n                <stringProp name=\"Header.name\">Accept-Language</stringProp>\n                <stringProp name=\"Header.value\">en-us</stringProp>\n              </elementProp>\n            </collectionProp>\n          </HeaderManager>\n          <hashTree/>\n          <GenericController guiclass=\"LogicControllerGui\" testclass=\"GenericController\" testname=\"Moodle tests\" enabled=\"true\"/>\n          <hashTree>\n            <UserParameters guiclass=\"UserParametersGui\" testclass=\"UserParameters\" testname=\"User Parameters\" enabled=\"true\">\n              <boolProp name=\"TestElement.per_iteration\">true</boolProp>\n              <collectionProp name=\"UserParameters.names\">\n                <stringProp name=\"VIEWSTATE\">VIEWSTATE</stringProp>\n                <stringProp name=\"jsessionid\">jsessionid</stringProp>\n                <stringProp name=\"-532667892\">RandomNumber</stringProp>\n                <stringProp name=\"-147148869\">userNum</stringProp>\n                <stringProp name=\"-265713450\">username</stringProp>\n                <stringProp name=\"-1797703761\">userRandomChoice</stringProp>\n                <stringProp name=\"1984989421\">sesskey</stringProp>\n                <stringProp name=\"636521343\">testBigFileLocation</stringProp>\n                <stringProp name=\"-1981424811\">testBigFileName</stringProp>\n                <stringProp name=\"775281475\">discussionid</stringProp>\n                <stringProp name=\"-1804370087\">courseList</stringProp>\n                <stringProp name=\"-2078389680\">xx_outputFromSplit</stringProp>\n              </collectionProp>\n              <collectionProp name=\"UserParameters.thread_values\">\n                <collectionProp name=\"-547776047\">\n                  <stringProp name=\"\"></stringProp>\n                  <stringProp name=\"\"></stringProp>\n                  <stringProp name=\"-2046088625\">${__Random(1,2000,ReuseRandomNumber)}</stringProp>\n                  <stringProp name=\"-909306826\">${__javaScript(Math.floor(ctx.getThreadGroup().getNumThreads()*props.getProperty(&quot;slaveNum&quot;\\,&quot;0&quot;)+ctx.getThreadNum()+1).toFixed(0))}</stringProp>\n                  <stringProp name=\"835931326\">m_azuretestuser_${userNum}</stringProp>\n                  <stringProp name=\"1345253498\">${RandomNumber}</stringProp>\n                  <stringProp name=\"-352988409\">${sesskey}</stringProp>\n                  <stringProp name=\"0\"></stringProp>\n                  <stringProp name=\"0\"></stringProp>\n                  <stringProp name=\"0\"></stringProp>\n                  <stringProp name=\"257720200\">${__V(course_list_${RandomNumber})}</stringProp>\n                  <stringProp name=\"-306911129\">${__split(${courseList}, course_id,|)}</stringProp>\n                </collectionProp>\n              </collectionProp>\n              <boolProp name=\"UserParameters.per_iteration\">true</boolProp>\n            </UserParameters>\n            <hashTree/>\n            <HTTPSamplerProxy guiclass=\"HttpTestSampleGui\" testclass=\"HTTPSamplerProxy\" testname=\"Goto Moodle\" enabled=\"true\">\n              <elementProp name=\"HTTPsampler.Arguments\" elementType=\"Arguments\" guiclass=\"HTTPArgumentsPanel\" testclass=\"Arguments\" testname=\"Argument List\" enabled=\"true\">\n                <collectionProp name=\"Arguments.arguments\"/>\n              </elementProp>\n              <stringProp name=\"HTTPSampler.domain\">${host}</stringProp>\n              <stringProp name=\"HTTPSampler.port\">${port}</stringProp>\n              <stringProp name=\"HTTPSampler.protocol\">${protocol}</stringProp>\n              <stringProp name=\"HTTPSampler.contentEncoding\"></stringProp>\n              <stringProp name=\"HTTPSampler.path\">/</stringProp>\n              <stringProp name=\"HTTPSampler.method\">GET</stringProp>\n              <boolProp name=\"HTTPSampler.follow_redirects\">true</boolProp>\n              <boolProp name=\"HTTPSampler.auto_redirects\">false</boolProp>\n              <boolProp name=\"HTTPSampler.use_keepalive\">true</boolProp>\n              <boolProp name=\"HTTPSampler.DO_MULTIPART_POST\">false</boolProp>\n              <stringProp name=\"HTTPSampler.embedded_url_re\"></stringProp>\n              <stringProp name=\"HTTPSampler.implementation\">Java</stringProp>\n              <stringProp name=\"HTTPSampler.connect_timeout\">${default_connect_timeout}</stringProp>\n              <stringProp name=\"HTTPSampler.response_timeout\">${default_response_timeout}</stringProp>\n            </HTTPSamplerProxy>\n            <hashTree>\n              <ConstantTimer guiclass=\"ConstantTimerGui\" testclass=\"ConstantTimer\" testname=\"Constant Timer\" enabled=\"false\">\n                <stringProp name=\"ConstantTimer.delay\">1000</stringProp>\n              </ConstantTimer>\n              <hashTree/>\n              <GaussianRandomTimer guiclass=\"GaussianRandomTimerGui\" testclass=\"GaussianRandomTimer\" testname=\"Gaussian Random Timer\" enabled=\"true\">\n                <stringProp name=\"ConstantTimer.delay\">8000</stringProp>\n                <stringProp name=\"RandomTimer.range\">4000</stringProp>\n              </GaussianRandomTimer>\n              <hashTree/>\n            </hashTree>\n            <HTTPSamplerProxy guiclass=\"HttpTestSampleGui\" testclass=\"HTTPSamplerProxy\" testname=\"POST login\" enabled=\"true\">\n              <elementProp name=\"HTTPsampler.Arguments\" elementType=\"Arguments\" guiclass=\"HTTPArgumentsPanel\" testclass=\"Arguments\" testname=\"Argument List\" enabled=\"true\">\n                <collectionProp name=\"Arguments.arguments\">\n                  <elementProp name=\"username\" elementType=\"HTTPArgument\">\n                    <stringProp name=\"HTTPArgument.always_encode\">true</stringProp>\n                    <stringProp name=\"Argument.value\">${username}</stringProp>\n                    <stringProp name=\"Argument.name\">username</stringProp>\n                    <stringProp name=\"Argument.metadata\">=</stringProp>\n                    <stringProp name=\"Argument.use_equals\">true</stringProp>\n                    <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                  </elementProp>\n                  <elementProp name=\"password\" elementType=\"HTTPArgument\">\n                    <stringProp name=\"HTTPArgument.always_encode\">true</stringProp>\n                    <stringProp name=\"Argument.value\">${moodle_user_pass}</stringProp>\n                    <stringProp name=\"Argument.name\">password</stringProp>\n                    <stringProp name=\"Argument.metadata\">=</stringProp>\n                    <stringProp name=\"Argument.use_equals\">true</stringProp>\n                    <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                  </elementProp>\n                  <elementProp name=\"Login\" elementType=\"HTTPArgument\">\n                    <boolProp name=\"HTTPArgument.always_encode\">true</boolProp>\n                    <stringProp name=\"Argument.value\">Login</stringProp>\n                    <stringProp name=\"Argument.metadata\">=</stringProp>\n                    <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                    <stringProp name=\"Argument.name\">Login</stringProp>\n                  </elementProp>\n                </collectionProp>\n              </elementProp>\n              <stringProp name=\"HTTPSampler.domain\">${login_host}</stringProp>\n              <stringProp name=\"HTTPSampler.port\">${port}</stringProp>\n              <stringProp name=\"HTTPSampler.protocol\">${protocol}</stringProp>\n              <stringProp name=\"HTTPSampler.contentEncoding\"></stringProp>\n              <stringProp name=\"HTTPSampler.path\">/login/index.php</stringProp>\n              <stringProp name=\"HTTPSampler.method\">POST</stringProp>\n              <boolProp name=\"HTTPSampler.follow_redirects\">true</boolProp>\n              <boolProp name=\"HTTPSampler.auto_redirects\">false</boolProp>\n              <boolProp name=\"HTTPSampler.use_keepalive\">true</boolProp>\n              <boolProp name=\"HTTPSampler.DO_MULTIPART_POST\">false</boolProp>\n              <boolProp name=\"HTTPSampler.image_parser\">true</boolProp>\n              <stringProp name=\"HTTPSampler.embedded_url_re\"></stringProp>\n              <stringProp name=\"HTTPSampler.implementation\">Java</stringProp>\n              <stringProp name=\"HTTPSampler.connect_timeout\">${default_connect_timeout}</stringProp>\n              <stringProp name=\"HTTPSampler.response_timeout\">${default_response_timeout}</stringProp>\n            </HTTPSamplerProxy>\n            <hashTree>\n              <ResponseAssertion guiclass=\"AssertionGui\" testclass=\"ResponseAssertion\" testname=\"Assertion - Logged in Screen\" enabled=\"true\">\n                <collectionProp name=\"Asserion.test_strings\">\n                  <stringProp name=\"-1544167836\">You are logged in as </stringProp>\n                </collectionProp>\n                <intProp name=\"Assertion.test_type\">2</intProp>\n                <stringProp name=\"Assertion.test_field\">Assertion.response_data</stringProp>\n                <boolProp name=\"Assertion.assume_success\">false</boolProp>\n                <stringProp name=\"Assertion.custom_message\"></stringProp>\n              </ResponseAssertion>\n              <hashTree/>\n              <XPathExtractor guiclass=\"XPathExtractorGui\" testclass=\"XPathExtractor\" testname=\"Get sesskey\" enabled=\"true\">\n                <stringProp name=\"XPathExtractor.default\"></stringProp>\n                <stringProp name=\"XPathExtractor.refname\">sesskey</stringProp>\n                <stringProp name=\"XPathExtractor.xpathQuery\">//*[@name=&apos;sesskey&apos;][1]/@value</stringProp>\n                <boolProp name=\"XPathExtractor.validate\">false</boolProp>\n                <boolProp name=\"XPathExtractor.tolerant\">true</boolProp>\n                <boolProp name=\"XPathExtractor.namespace\">false</boolProp>\n                <stringProp name=\"XPathExtractor.matchNumber\">-1</stringProp>\n              </XPathExtractor>\n              <hashTree/>\n              <ConstantTimer guiclass=\"ConstantTimerGui\" testclass=\"ConstantTimer\" testname=\"Constant Timer\" enabled=\"false\">\n                <stringProp name=\"ConstantTimer.delay\">1000</stringProp>\n              </ConstantTimer>\n              <hashTree/>\n              <GaussianRandomTimer guiclass=\"GaussianRandomTimerGui\" testclass=\"GaussianRandomTimer\" testname=\"Gaussian Random Timer\" enabled=\"true\">\n                <stringProp name=\"ConstantTimer.delay\">5000</stringProp>\n                <stringProp name=\"RandomTimer.range\">0</stringProp>\n              </GaussianRandomTimer>\n              <hashTree/>\n              <ResultCollector guiclass=\"ViewResultsFullVisualizer\" testclass=\"ResultCollector\" testname=\"View Results Tree\" enabled=\"false\">\n                <boolProp name=\"ResultCollector.error_logging\">false</boolProp>\n                <objProp>\n                  <name>saveConfig</name>\n                  <value class=\"SampleSaveConfiguration\">\n                    <time>true</time>\n                    <latency>true</latency>\n                    <timestamp>true</timestamp>\n                    <success>true</success>\n                    <label>true</label>\n                    <code>true</code>\n                    <message>true</message>\n                    <threadName>true</threadName>\n                    <dataType>true</dataType>\n                    <encoding>false</encoding>\n                    <assertions>true</assertions>\n                    <subresults>true</subresults>\n                    <responseData>false</responseData>\n                    <samplerData>false</samplerData>\n                    <xml>false</xml>\n                    <fieldNames>true</fieldNames>\n                    <responseHeaders>false</responseHeaders>\n                    <requestHeaders>false</requestHeaders>\n                    <responseDataOnError>false</responseDataOnError>\n                    <saveAssertionResultsFailureMessage>true</saveAssertionResultsFailureMessage>\n                    <assertionsResultsToSave>0</assertionsResultsToSave>\n                    <bytes>true</bytes>\n                    <sentBytes>true</sentBytes>\n                    <threadCounts>true</threadCounts>\n                    <idleTime>true</idleTime>\n                    <connectTime>true</connectTime>\n                  </value>\n                </objProp>\n                <stringProp name=\"filename\"></stringProp>\n              </ResultCollector>\n              <hashTree/>\n            </hashTree>\n            <LoopController guiclass=\"LoopControlPanel\" testclass=\"LoopController\" testname=\"Loop\" enabled=\"true\">\n              <boolProp name=\"LoopController.continue_forever\">true</boolProp>\n              <stringProp name=\"LoopController.loops\">10</stringProp>\n            </LoopController>\n            <hashTree>\n              <GenericController guiclass=\"LogicControllerGui\" testclass=\"GenericController\" testname=\"Try Quiz\" enabled=\"true\"/>\n              <hashTree>\n                <HTTPSamplerProxy guiclass=\"HttpTestSampleGui\" testclass=\"HTTPSamplerProxy\" testname=\"Goto course\" enabled=\"true\">\n                  <elementProp name=\"HTTPsampler.Arguments\" elementType=\"Arguments\" guiclass=\"HTTPArgumentsPanel\" testclass=\"Arguments\" testname=\"Argument List\" enabled=\"true\">\n                    <collectionProp name=\"Arguments.arguments\">\n                      <elementProp name=\"id\" elementType=\"HTTPArgument\">\n                        <boolProp name=\"HTTPArgument.always_encode\">false</boolProp>\n                        <stringProp name=\"Argument.value\">${activity_course_id}</stringProp>\n                        <stringProp name=\"Argument.name\">id</stringProp>\n                        <stringProp name=\"Argument.metadata\">=</stringProp>\n                        <stringProp name=\"Argument.use_equals\">true</stringProp>\n                        <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                      </elementProp>\n                    </collectionProp>\n                  </elementProp>\n                  <stringProp name=\"HTTPSampler.domain\">${host}</stringProp>\n                  <stringProp name=\"HTTPSampler.port\">${port}</stringProp>\n                  <stringProp name=\"HTTPSampler.protocol\">${protocol}</stringProp>\n                  <stringProp name=\"HTTPSampler.contentEncoding\"></stringProp>\n                  <stringProp name=\"HTTPSampler.path\">/course/view.php</stringProp>\n                  <stringProp name=\"HTTPSampler.method\">GET</stringProp>\n                  <boolProp name=\"HTTPSampler.follow_redirects\">true</boolProp>\n                  <boolProp name=\"HTTPSampler.auto_redirects\">false</boolProp>\n                  <boolProp name=\"HTTPSampler.use_keepalive\">true</boolProp>\n                  <boolProp name=\"HTTPSampler.DO_MULTIPART_POST\">false</boolProp>\n                  <stringProp name=\"HTTPSampler.embedded_url_re\"></stringProp>\n                  <stringProp name=\"HTTPSampler.implementation\">Java</stringProp>\n                  <stringProp name=\"HTTPSampler.connect_timeout\"></stringProp>\n                  <stringProp name=\"HTTPSampler.response_timeout\"></stringProp>\n                </HTTPSamplerProxy>\n                <hashTree>\n                  <ResponseAssertion guiclass=\"AssertionGui\" testclass=\"ResponseAssertion\" testname=\"Assertion - in the course ?\" enabled=\"true\">\n                    <collectionProp name=\"Asserion.test_strings\">\n                      <stringProp name=\"-1205900583\">Performance Testing Regular Quiz</stringProp>\n                    </collectionProp>\n                    <intProp name=\"Assertion.test_type\">2</intProp>\n                    <stringProp name=\"Assertion.test_field\">Assertion.response_data</stringProp>\n                    <boolProp name=\"Assertion.assume_success\">true</boolProp>\n                    <stringProp name=\"Assertion.custom_message\"></stringProp>\n                  </ResponseAssertion>\n                  <hashTree/>\n                  <RegexExtractor guiclass=\"RegexExtractorGui\" testclass=\"RegexExtractor\" testname=\"RegExp Extractor - Get forum discussion headings (if any!)\" enabled=\"false\">\n                    <stringProp name=\"RegexExtractor.useHeaders\">false</stringProp>\n                    <stringProp name=\"RegexExtractor.refname\">classtopics_html</stringProp>\n                    <stringProp name=\"RegexExtractor.regex\">(?s)(&lt;ul class=&quot;topics&quot;.*?)&lt;\\/div&gt;.*?&lt;\\/div&gt;.*?&lt;\\/div&gt;.*?&lt;\\/div&gt;.*?&lt;\\/div&gt;.*?&lt;div id=&quot;region-pre&quot;</stringProp>\n                    <stringProp name=\"RegexExtractor.template\">$1$</stringProp>\n                    <stringProp name=\"RegexExtractor.default\">NULL</stringProp>\n                    <stringProp name=\"RegexExtractor.match_number\"></stringProp>\n                  </RegexExtractor>\n                  <hashTree/>\n                  <RegexExtractor guiclass=\"RegexExtractorGui\" testclass=\"RegexExtractor\" testname=\"RegExp Extractor - Get perf quiz chunk\" enabled=\"true\">\n                    <stringProp name=\"RegexExtractor.useHeaders\">false</stringProp>\n                    <stringProp name=\"RegexExtractor.refname\">html_chunk_perfquiz</stringProp>\n                    <stringProp name=\"RegexExtractor.regex\">(.{0,250}Performance Testing Regular Quiz)</stringProp>\n                    <stringProp name=\"RegexExtractor.template\">$1$</stringProp>\n                    <stringProp name=\"RegexExtractor.default\">NULL</stringProp>\n                    <stringProp name=\"RegexExtractor.match_number\"></stringProp>\n                  </RegexExtractor>\n                  <hashTree/>\n                  <RegexExtractor guiclass=\"RegexExtractorGui\" testclass=\"RegexExtractor\" testname=\"RegExp Extractor - Get forum discussion headings (if any!)\" enabled=\"true\">\n                    <stringProp name=\"RegexExtractor.useHeaders\">false</stringProp>\n                    <stringProp name=\"RegexExtractor.refname\">moduleid</stringProp>\n                    <stringProp name=\"RegexExtractor.regex\">\\/mod\\/quiz\\/view\\.php\\?id=(\\d+)</stringProp>\n                    <stringProp name=\"RegexExtractor.template\">$1$</stringProp>\n                    <stringProp name=\"RegexExtractor.default\">NULL</stringProp>\n                    <stringProp name=\"RegexExtractor.match_number\"></stringProp>\n                    <stringProp name=\"Sample.scope\">variable</stringProp>\n                    <stringProp name=\"Scope.variable\">html_chunk_perfquiz</stringProp>\n                  </RegexExtractor>\n                  <hashTree/>\n                  <XPathExtractor guiclass=\"XPathExtractorGui\" testclass=\"XPathExtractor\" testname=\"XPath Extractor - Get Performance Test Regular Quiz id\" enabled=\"false\">\n                    <stringProp name=\"XPathExtractor.default\">NULL</stringProp>\n                    <stringProp name=\"XPathExtractor.refname\">moduleid</stringProp>\n                    <stringProp name=\"XPathExtractor.xpathQuery\">substring-after(//*[contains(text(),&apos;Performance Testing Regular Quiz&apos;)]/../@href,&apos;id=&apos;)</stringProp>\n                    <boolProp name=\"XPathExtractor.validate\">false</boolProp>\n                    <boolProp name=\"XPathExtractor.tolerant\">true</boolProp>\n                    <boolProp name=\"XPathExtractor.namespace\">false</boolProp>\n                    <boolProp name=\"XPathExtractor.show_warnings\">true</boolProp>\n                    <boolProp name=\"XPathExtractor.quiet\">false</boolProp>\n                    <boolProp name=\"XPathExtractor.report_errors\">true</boolProp>\n                    <stringProp name=\"Sample.scope\">variable</stringProp>\n                    <stringProp name=\"Scope.variable\">classtopics_html</stringProp>\n                    <stringProp name=\"XPathExtractor.matchNumber\">-1</stringProp>\n                  </XPathExtractor>\n                  <hashTree/>\n                  <XPathExtractor guiclass=\"XPathExtractorGui\" testclass=\"XPathExtractor\" testname=\"Get module id\" enabled=\"false\">\n                    <stringProp name=\"XPathExtractor.default\"></stringProp>\n                    <stringProp name=\"XPathExtractor.refname\">moduleid</stringProp>\n                    <stringProp name=\"XPathExtractor.xpathQuery\">substring-after(//*/a/*[contains(text(),&apos;Performance Testing Regular Quiz&apos;)]/../@href,&apos;=&apos;)</stringProp>\n                    <boolProp name=\"XPathExtractor.validate\">false</boolProp>\n                    <boolProp name=\"XPathExtractor.tolerant\">true</boolProp>\n                    <boolProp name=\"XPathExtractor.namespace\">false</boolProp>\n                    <boolProp name=\"XPathExtractor.show_warnings\">true</boolProp>\n                    <boolProp name=\"XPathExtractor.report_errors\">true</boolProp>\n                    <boolProp name=\"XPathExtractor.quiet\">false</boolProp>\n                    <stringProp name=\"XPathExtractor.matchNumber\">-1</stringProp>\n                  </XPathExtractor>\n                  <hashTree/>\n                  <GaussianRandomTimer guiclass=\"GaussianRandomTimerGui\" testclass=\"GaussianRandomTimer\" testname=\"Gaussian Random Timer\" enabled=\"true\">\n                    <stringProp name=\"ConstantTimer.delay\">2000</stringProp>\n                    <stringProp name=\"RandomTimer.range\">1000</stringProp>\n                  </GaussianRandomTimer>\n                  <hashTree/>\n                  <ConstantTimer guiclass=\"ConstantTimerGui\" testclass=\"ConstantTimer\" testname=\"Constant Timer\" enabled=\"false\">\n                    <stringProp name=\"ConstantTimer.delay\">1000</stringProp>\n                  </ConstantTimer>\n                  <hashTree/>\n                  <ResultCollector guiclass=\"ViewResultsFullVisualizer\" testclass=\"ResultCollector\" testname=\"View Results Tree\" enabled=\"false\">\n                    <boolProp name=\"ResultCollector.error_logging\">false</boolProp>\n                    <objProp>\n                      <name>saveConfig</name>\n                      <value class=\"SampleSaveConfiguration\">\n                        <time>true</time>\n                        <latency>true</latency>\n                        <timestamp>true</timestamp>\n                        <success>true</success>\n                        <label>true</label>\n                        <code>true</code>\n                        <message>true</message>\n                        <threadName>true</threadName>\n                        <dataType>true</dataType>\n                        <encoding>false</encoding>\n                        <assertions>true</assertions>\n                        <subresults>true</subresults>\n                        <responseData>false</responseData>\n                        <samplerData>false</samplerData>\n                        <xml>false</xml>\n                        <fieldNames>true</fieldNames>\n                        <responseHeaders>false</responseHeaders>\n                        <requestHeaders>false</requestHeaders>\n                        <responseDataOnError>false</responseDataOnError>\n                        <saveAssertionResultsFailureMessage>true</saveAssertionResultsFailureMessage>\n                        <assertionsResultsToSave>0</assertionsResultsToSave>\n                        <bytes>true</bytes>\n                        <sentBytes>true</sentBytes>\n                        <threadCounts>true</threadCounts>\n                        <idleTime>true</idleTime>\n                        <connectTime>true</connectTime>\n                      </value>\n                    </objProp>\n                    <stringProp name=\"filename\"></stringProp>\n                  </ResultCollector>\n                  <hashTree/>\n                </hashTree>\n                <WhileController guiclass=\"WhileControllerGui\" testclass=\"WhileController\" testname=\"Poll while quiz hasn&apos;t started\" enabled=\"true\">\n                  <stringProp name=\"WhileController.condition\">${__javaScript(&quot;${quiz_opened}&quot; != &quot;This quiz opened at&quot;)}</stringProp>\n                </WhileController>\n                <hashTree>\n                  <HTTPSamplerProxy guiclass=\"HttpTestSampleGui\" testclass=\"HTTPSamplerProxy\" testname=\"Open the quiz\" enabled=\"true\">\n                    <elementProp name=\"HTTPsampler.Arguments\" elementType=\"Arguments\" guiclass=\"HTTPArgumentsPanel\" testclass=\"Arguments\" testname=\"Argument List\" enabled=\"true\">\n                      <collectionProp name=\"Arguments.arguments\">\n                        <elementProp name=\"id\" elementType=\"HTTPArgument\">\n                          <boolProp name=\"HTTPArgument.always_encode\">false</boolProp>\n                          <stringProp name=\"Argument.value\">${moduleid}</stringProp>\n                          <stringProp name=\"Argument.name\">id</stringProp>\n                          <stringProp name=\"Argument.metadata\">=</stringProp>\n                          <stringProp name=\"Argument.use_equals\">true</stringProp>\n                          <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                        </elementProp>\n                      </collectionProp>\n                    </elementProp>\n                    <stringProp name=\"HTTPSampler.domain\">${host}</stringProp>\n                    <stringProp name=\"HTTPSampler.port\">${port}</stringProp>\n                    <stringProp name=\"HTTPSampler.protocol\">${protocol}</stringProp>\n                    <stringProp name=\"HTTPSampler.contentEncoding\"></stringProp>\n                    <stringProp name=\"HTTPSampler.path\">/mod/quiz/view.php</stringProp>\n                    <stringProp name=\"HTTPSampler.method\">GET</stringProp>\n                    <boolProp name=\"HTTPSampler.follow_redirects\">true</boolProp>\n                    <boolProp name=\"HTTPSampler.auto_redirects\">false</boolProp>\n                    <boolProp name=\"HTTPSampler.use_keepalive\">true</boolProp>\n                    <boolProp name=\"HTTPSampler.DO_MULTIPART_POST\">false</boolProp>\n                    <stringProp name=\"HTTPSampler.embedded_url_re\"></stringProp>\n                    <stringProp name=\"HTTPSampler.implementation\">Java</stringProp>\n                    <stringProp name=\"HTTPSampler.connect_timeout\"></stringProp>\n                    <stringProp name=\"HTTPSampler.response_timeout\"></stringProp>\n                  </HTTPSamplerProxy>\n                  <hashTree>\n                    <RegexExtractor guiclass=\"RegexExtractorGui\" testclass=\"RegexExtractor\" testname=\"Regular Expression Extractor - Check if quiz is open now\" enabled=\"true\">\n                      <stringProp name=\"RegexExtractor.useHeaders\">false</stringProp>\n                      <stringProp name=\"RegexExtractor.refname\">quiz_opened</stringProp>\n                      <stringProp name=\"RegexExtractor.regex\">(This quiz opened at)</stringProp>\n                      <stringProp name=\"RegexExtractor.template\">$1$</stringProp>\n                      <stringProp name=\"RegexExtractor.default\"></stringProp>\n                      <stringProp name=\"RegexExtractor.match_number\"></stringProp>\n                      <boolProp name=\"RegexExtractor.default_empty_value\">true</boolProp>\n                    </RegexExtractor>\n                    <hashTree/>\n                    <ResponseAssertion guiclass=\"AssertionGui\" testclass=\"ResponseAssertion\" testname=\"Assertion - quiz hasn&apos;t started?\" enabled=\"false\">\n                      <collectionProp name=\"Asserion.test_strings\">\n                        <stringProp name=\"956891785\">The quiz will not be available until</stringProp>\n                      </collectionProp>\n                      <stringProp name=\"Assertion.test_field\">Assertion.response_data</stringProp>\n                      <boolProp name=\"Assertion.assume_success\">false</boolProp>\n                      <intProp name=\"Assertion.test_type\">2</intProp>\n                      <stringProp name=\"Assertion.custom_message\"></stringProp>\n                    </ResponseAssertion>\n                    <hashTree/>\n                    <XPathExtractor guiclass=\"XPathExtractorGui\" testclass=\"XPathExtractor\" testname=\"Get quiz id\" enabled=\"false\">\n                      <stringProp name=\"XPathExtractor.default\"></stringProp>\n                      <stringProp name=\"XPathExtractor.refname\">quizid</stringProp>\n                      <stringProp name=\"XPathExtractor.xpathQuery\">//*/form[@action=&quot;https://${host}/mod/quiz/startattempt.php&quot;]//*/input[@name=&apos;cmid&apos;][1]/@value</stringProp>\n                      <boolProp name=\"XPathExtractor.validate\">false</boolProp>\n                      <boolProp name=\"XPathExtractor.tolerant\">true</boolProp>\n                      <boolProp name=\"XPathExtractor.namespace\">false</boolProp>\n                      <boolProp name=\"XPathExtractor.show_warnings\">true</boolProp>\n                      <boolProp name=\"XPathExtractor.report_errors\">true</boolProp>\n                      <boolProp name=\"XPathExtractor.quiet\">false</boolProp>\n                      <stringProp name=\"XPathExtractor.matchNumber\">-1</stringProp>\n                    </XPathExtractor>\n                    <hashTree/>\n                    <GaussianRandomTimer guiclass=\"GaussianRandomTimerGui\" testclass=\"GaussianRandomTimer\" testname=\"Gaussian Random Timer\" enabled=\"true\">\n                      <stringProp name=\"ConstantTimer.delay\">500</stringProp>\n                      <stringProp name=\"RandomTimer.range\">200</stringProp>\n                    </GaussianRandomTimer>\n                    <hashTree/>\n                    <ConstantTimer guiclass=\"ConstantTimerGui\" testclass=\"ConstantTimer\" testname=\"Constant Timer\" enabled=\"false\">\n                      <stringProp name=\"ConstantTimer.delay\">1000</stringProp>\n                    </ConstantTimer>\n                    <hashTree/>\n                    <ResultCollector guiclass=\"ViewResultsFullVisualizer\" testclass=\"ResultCollector\" testname=\"View Results Tree\" enabled=\"false\">\n                      <boolProp name=\"ResultCollector.error_logging\">false</boolProp>\n                      <objProp>\n                        <name>saveConfig</name>\n                        <value class=\"SampleSaveConfiguration\">\n                          <time>true</time>\n                          <latency>true</latency>\n                          <timestamp>true</timestamp>\n                          <success>true</success>\n                          <label>true</label>\n                          <code>true</code>\n                          <message>true</message>\n                          <threadName>true</threadName>\n                          <dataType>true</dataType>\n                          <encoding>false</encoding>\n                          <assertions>true</assertions>\n                          <subresults>true</subresults>\n                          <responseData>false</responseData>\n                          <samplerData>false</samplerData>\n                          <xml>false</xml>\n                          <fieldNames>true</fieldNames>\n                          <responseHeaders>false</responseHeaders>\n                          <requestHeaders>false</requestHeaders>\n                          <responseDataOnError>false</responseDataOnError>\n                          <saveAssertionResultsFailureMessage>true</saveAssertionResultsFailureMessage>\n                          <assertionsResultsToSave>0</assertionsResultsToSave>\n                          <bytes>true</bytes>\n                          <sentBytes>true</sentBytes>\n                          <threadCounts>true</threadCounts>\n                          <idleTime>true</idleTime>\n                          <connectTime>true</connectTime>\n                        </value>\n                      </objProp>\n                      <stringProp name=\"filename\"></stringProp>\n                    </ResultCollector>\n                    <hashTree/>\n                  </hashTree>\n                </hashTree>\n                <HTTPSamplerProxy guiclass=\"HttpTestSampleGui\" testclass=\"HTTPSamplerProxy\" testname=\"Click on Attempt the quiz now\" enabled=\"true\">\n                  <elementProp name=\"HTTPsampler.Arguments\" elementType=\"Arguments\" guiclass=\"HTTPArgumentsPanel\" testclass=\"Arguments\" testname=\"Argument List\" enabled=\"true\">\n                    <collectionProp name=\"Arguments.arguments\">\n                      <elementProp name=\"cmid\" elementType=\"HTTPArgument\">\n                        <boolProp name=\"HTTPArgument.always_encode\">false</boolProp>\n                        <stringProp name=\"Argument.value\">${moduleid}</stringProp>\n                        <stringProp name=\"Argument.name\">cmid</stringProp>\n                        <stringProp name=\"Argument.metadata\">=</stringProp>\n                        <stringProp name=\"Argument.use_equals\">true</stringProp>\n                        <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                      </elementProp>\n                      <elementProp name=\"sesskey\" elementType=\"HTTPArgument\">\n                        <boolProp name=\"HTTPArgument.always_encode\">false</boolProp>\n                        <stringProp name=\"Argument.value\">${sesskey}</stringProp>\n                        <stringProp name=\"Argument.metadata\">=</stringProp>\n                        <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                        <stringProp name=\"Argument.name\">sesskey</stringProp>\n                      </elementProp>\n                    </collectionProp>\n                  </elementProp>\n                  <stringProp name=\"HTTPSampler.domain\">${host}</stringProp>\n                  <stringProp name=\"HTTPSampler.port\">${port}</stringProp>\n                  <stringProp name=\"HTTPSampler.protocol\">${protocol}</stringProp>\n                  <stringProp name=\"HTTPSampler.contentEncoding\"></stringProp>\n                  <stringProp name=\"HTTPSampler.path\">/mod/quiz/startattempt.php</stringProp>\n                  <stringProp name=\"HTTPSampler.method\">POST</stringProp>\n                  <boolProp name=\"HTTPSampler.follow_redirects\">true</boolProp>\n                  <boolProp name=\"HTTPSampler.auto_redirects\">false</boolProp>\n                  <boolProp name=\"HTTPSampler.use_keepalive\">true</boolProp>\n                  <boolProp name=\"HTTPSampler.DO_MULTIPART_POST\">false</boolProp>\n                  <stringProp name=\"HTTPSampler.embedded_url_re\"></stringProp>\n                  <stringProp name=\"HTTPSampler.implementation\">Java</stringProp>\n                  <stringProp name=\"HTTPSampler.connect_timeout\"></stringProp>\n                  <stringProp name=\"HTTPSampler.response_timeout\"></stringProp>\n                </HTTPSamplerProxy>\n                <hashTree>\n                  <ResponseAssertion guiclass=\"AssertionGui\" testclass=\"ResponseAssertion\" testname=\"Assertion - in the quiz ?\" enabled=\"true\">\n                    <collectionProp name=\"Asserion.test_strings\">\n                      <stringProp name=\"-1545612013\">What&apos;s your username</stringProp>\n                      <stringProp name=\"-2127233155\">Is the LMS Performing acceptably?</stringProp>\n                      <stringProp name=\"-1512513172\">What is 2 \\+ 2\\?</stringProp>\n                    </collectionProp>\n                    <stringProp name=\"Assertion.test_field\">Assertion.response_data</stringProp>\n                    <boolProp name=\"Assertion.assume_success\">false</boolProp>\n                    <intProp name=\"Assertion.test_type\">2</intProp>\n                    <stringProp name=\"Assertion.custom_message\"></stringProp>\n                  </ResponseAssertion>\n                  <hashTree/>\n                  <GaussianRandomTimer guiclass=\"GaussianRandomTimerGui\" testclass=\"GaussianRandomTimer\" testname=\"Gaussian Random Timer\" enabled=\"true\">\n                    <stringProp name=\"ConstantTimer.delay\">2000</stringProp>\n                    <stringProp name=\"RandomTimer.range\">1000</stringProp>\n                  </GaussianRandomTimer>\n                  <hashTree/>\n                  <ConstantTimer guiclass=\"ConstantTimerGui\" testclass=\"ConstantTimer\" testname=\"Constant Timer\" enabled=\"false\">\n                    <stringProp name=\"ConstantTimer.delay\">1000</stringProp>\n                  </ConstantTimer>\n                  <hashTree/>\n                  <XPathExtractor guiclass=\"XPathExtractorGui\" testclass=\"XPathExtractor\" testname=\"Get attemptid\" enabled=\"false\">\n                    <stringProp name=\"XPathExtractor.default\"></stringProp>\n                    <stringProp name=\"XPathExtractor.refname\">attemptid</stringProp>\n                    <stringProp name=\"XPathExtractor.xpathQuery\">//*/form[@action=&quot;https://${host}/mod/quiz/processattempt.php&quot;]//*/input[@name=&apos;attempt&apos;][1]/@value</stringProp>\n                    <boolProp name=\"XPathExtractor.validate\">false</boolProp>\n                    <boolProp name=\"XPathExtractor.tolerant\">true</boolProp>\n                    <boolProp name=\"XPathExtractor.namespace\">false</boolProp>\n                    <boolProp name=\"XPathExtractor.show_warnings\">true</boolProp>\n                    <boolProp name=\"XPathExtractor.report_errors\">true</boolProp>\n                    <boolProp name=\"XPathExtractor.quiet\">false</boolProp>\n                    <stringProp name=\"XPathExtractor.matchNumber\">-1</stringProp>\n                  </XPathExtractor>\n                  <hashTree/>\n                  <RegexExtractor guiclass=\"RegexExtractorGui\" testclass=\"RegexExtractor\" testname=\"RegExp - Get attemptip\" enabled=\"true\">\n                    <stringProp name=\"RegexExtractor.useHeaders\">false</stringProp>\n                    <stringProp name=\"RegexExtractor.refname\">attemptid</stringProp>\n                    <stringProp name=\"RegexExtractor.regex\">\\/mod\\/quiz\\/attempt\\.php\\?attempt=(\\d+).*?&quot;&gt;Continue&lt;\\/a&gt;</stringProp>\n                    <stringProp name=\"RegexExtractor.template\">$1$</stringProp>\n                    <stringProp name=\"RegexExtractor.default\">NULL</stringProp>\n                    <stringProp name=\"RegexExtractor.match_number\"></stringProp>\n                    <stringProp name=\"Sample.scope\">children</stringProp>\n                  </RegexExtractor>\n                  <hashTree/>\n                  <ResultCollector guiclass=\"ViewResultsFullVisualizer\" testclass=\"ResultCollector\" testname=\"View Results Tree\" enabled=\"false\">\n                    <boolProp name=\"ResultCollector.error_logging\">false</boolProp>\n                    <objProp>\n                      <name>saveConfig</name>\n                      <value class=\"SampleSaveConfiguration\">\n                        <time>true</time>\n                        <latency>true</latency>\n                        <timestamp>true</timestamp>\n                        <success>true</success>\n                        <label>true</label>\n                        <code>true</code>\n                        <message>true</message>\n                        <threadName>true</threadName>\n                        <dataType>true</dataType>\n                        <encoding>false</encoding>\n                        <assertions>true</assertions>\n                        <subresults>true</subresults>\n                        <responseData>false</responseData>\n                        <samplerData>false</samplerData>\n                        <xml>false</xml>\n                        <fieldNames>true</fieldNames>\n                        <responseHeaders>false</responseHeaders>\n                        <requestHeaders>false</requestHeaders>\n                        <responseDataOnError>false</responseDataOnError>\n                        <saveAssertionResultsFailureMessage>true</saveAssertionResultsFailureMessage>\n                        <assertionsResultsToSave>0</assertionsResultsToSave>\n                        <bytes>true</bytes>\n                        <sentBytes>true</sentBytes>\n                        <threadCounts>true</threadCounts>\n                        <idleTime>true</idleTime>\n                        <connectTime>true</connectTime>\n                      </value>\n                    </objProp>\n                    <stringProp name=\"filename\"></stringProp>\n                  </ResultCollector>\n                  <hashTree/>\n                </hashTree>\n                <HTTPSamplerProxy guiclass=\"HttpTestSampleGui\" testclass=\"HTTPSamplerProxy\" testname=\"Submit the quiz attempt\" enabled=\"true\">\n                  <elementProp name=\"HTTPsampler.Arguments\" elementType=\"Arguments\" guiclass=\"HTTPArgumentsPanel\" testclass=\"Arguments\" testname=\"Argument List\" enabled=\"true\">\n                    <collectionProp name=\"Arguments.arguments\">\n                      <elementProp name=\"q${attemptid}:1_:flagged\" elementType=\"HTTPArgument\">\n                        <boolProp name=\"HTTPArgument.always_encode\">false</boolProp>\n                        <stringProp name=\"Argument.value\">0</stringProp>\n                        <stringProp name=\"Argument.metadata\">=</stringProp>\n                        <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                        <stringProp name=\"Argument.name\">q${attemptid}:1_:flagged</stringProp>\n                      </elementProp>\n                      <elementProp name=\"q${attemptid}:1_:sequencecheck\" elementType=\"HTTPArgument\">\n                        <boolProp name=\"HTTPArgument.always_encode\">false</boolProp>\n                        <stringProp name=\"Argument.value\">1</stringProp>\n                        <stringProp name=\"Argument.metadata\">=</stringProp>\n                        <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                        <stringProp name=\"Argument.name\">q${attemptid}:1_:sequencecheck</stringProp>\n                      </elementProp>\n                      <elementProp name=\"q${attemptid}:1_answer\" elementType=\"HTTPArgument\">\n                        <boolProp name=\"HTTPArgument.always_encode\">false</boolProp>\n                        <stringProp name=\"Argument.value\">${username}@${user_type}</stringProp>\n                        <stringProp name=\"Argument.metadata\">=</stringProp>\n                        <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                        <stringProp name=\"Argument.name\">q${attemptid}:1_answer</stringProp>\n                      </elementProp>\n                      <elementProp name=\"q${attemptid}:2_:flagged\" elementType=\"HTTPArgument\">\n                        <boolProp name=\"HTTPArgument.always_encode\">false</boolProp>\n                        <stringProp name=\"Argument.value\">0</stringProp>\n                        <stringProp name=\"Argument.metadata\">=</stringProp>\n                        <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                        <stringProp name=\"Argument.name\">q${attemptid}:2_:flagged</stringProp>\n                      </elementProp>\n                      <elementProp name=\"q${attemptid}:2_:sequencecheck\" elementType=\"HTTPArgument\">\n                        <boolProp name=\"HTTPArgument.always_encode\">false</boolProp>\n                        <stringProp name=\"Argument.value\">1</stringProp>\n                        <stringProp name=\"Argument.metadata\">=</stringProp>\n                        <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                        <stringProp name=\"Argument.name\">q${attemptid}:2_:sequencecheck</stringProp>\n                      </elementProp>\n                      <elementProp name=\"q${attemptid}:2_answer\" elementType=\"HTTPArgument\">\n                        <boolProp name=\"HTTPArgument.always_encode\">false</boolProp>\n                        <stringProp name=\"Argument.value\">${__Random(0,1)}</stringProp>\n                        <stringProp name=\"Argument.metadata\">=</stringProp>\n                        <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                        <stringProp name=\"Argument.name\">q${attemptid}:2_answer</stringProp>\n                      </elementProp>\n                      <elementProp name=\"q${attemptid}:3_:flagged\" elementType=\"HTTPArgument\">\n                        <boolProp name=\"HTTPArgument.always_encode\">false</boolProp>\n                        <stringProp name=\"Argument.value\">0</stringProp>\n                        <stringProp name=\"Argument.metadata\">=</stringProp>\n                        <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                        <stringProp name=\"Argument.name\">q${attemptid}:3_:flagged</stringProp>\n                      </elementProp>\n                      <elementProp name=\"q${attemptid}:3_:sequencecheck\" elementType=\"HTTPArgument\">\n                        <boolProp name=\"HTTPArgument.always_encode\">false</boolProp>\n                        <stringProp name=\"Argument.value\">1</stringProp>\n                        <stringProp name=\"Argument.metadata\">=</stringProp>\n                        <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                        <stringProp name=\"Argument.name\">q${attemptid}:3_:sequencecheck</stringProp>\n                      </elementProp>\n                      <elementProp name=\"q${attemptid}:3_answer\" elementType=\"HTTPArgument\">\n                        <boolProp name=\"HTTPArgument.always_encode\">false</boolProp>\n                        <stringProp name=\"Argument.value\">${__Random(3,5)}</stringProp>\n                        <stringProp name=\"Argument.metadata\">=</stringProp>\n                        <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                        <stringProp name=\"Argument.name\">q${attemptid}:3_answer</stringProp>\n                      </elementProp>\n                      <elementProp name=\"next\" elementType=\"HTTPArgument\">\n                        <boolProp name=\"HTTPArgument.always_encode\">false</boolProp>\n                        <stringProp name=\"Argument.value\">Next</stringProp>\n                        <stringProp name=\"Argument.metadata\">=</stringProp>\n                        <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                        <stringProp name=\"Argument.name\">next</stringProp>\n                      </elementProp>\n                      <elementProp name=\"attempt\" elementType=\"HTTPArgument\">\n                        <boolProp name=\"HTTPArgument.always_encode\">false</boolProp>\n                        <stringProp name=\"Argument.value\">${attemptid}</stringProp>\n                        <stringProp name=\"Argument.metadata\">=</stringProp>\n                        <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                        <stringProp name=\"Argument.name\">attempt</stringProp>\n                      </elementProp>\n                      <elementProp name=\"thispage\" elementType=\"HTTPArgument\">\n                        <boolProp name=\"HTTPArgument.always_encode\">false</boolProp>\n                        <stringProp name=\"Argument.value\">0</stringProp>\n                        <stringProp name=\"Argument.metadata\">=</stringProp>\n                        <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                        <stringProp name=\"Argument.name\">thispage</stringProp>\n                      </elementProp>\n                      <elementProp name=\"nextpage\" elementType=\"HTTPArgument\">\n                        <boolProp name=\"HTTPArgument.always_encode\">false</boolProp>\n                        <stringProp name=\"Argument.value\">-1</stringProp>\n                        <stringProp name=\"Argument.metadata\">=</stringProp>\n                        <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                        <stringProp name=\"Argument.name\">nextpage</stringProp>\n                      </elementProp>\n                      <elementProp name=\"timeup\" elementType=\"HTTPArgument\">\n                        <boolProp name=\"HTTPArgument.always_encode\">false</boolProp>\n                        <stringProp name=\"Argument.value\">0</stringProp>\n                        <stringProp name=\"Argument.metadata\">=</stringProp>\n                        <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                        <stringProp name=\"Argument.name\">timeup</stringProp>\n                      </elementProp>\n                      <elementProp name=\"sesskey\" elementType=\"HTTPArgument\">\n                        <boolProp name=\"HTTPArgument.always_encode\">false</boolProp>\n                        <stringProp name=\"Argument.value\">${sesskey}</stringProp>\n                        <stringProp name=\"Argument.metadata\">=</stringProp>\n                        <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                        <stringProp name=\"Argument.name\">sesskey</stringProp>\n                      </elementProp>\n                      <elementProp name=\"scrollpos\" elementType=\"HTTPArgument\">\n                        <boolProp name=\"HTTPArgument.always_encode\">false</boolProp>\n                        <stringProp name=\"Argument.value\"></stringProp>\n                        <stringProp name=\"Argument.metadata\">=</stringProp>\n                        <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                        <stringProp name=\"Argument.name\">scrollpos</stringProp>\n                      </elementProp>\n                      <elementProp name=\"slots\" elementType=\"HTTPArgument\">\n                        <boolProp name=\"HTTPArgument.always_encode\">false</boolProp>\n                        <stringProp name=\"Argument.value\">1,2,3</stringProp>\n                        <stringProp name=\"Argument.metadata\">=</stringProp>\n                        <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                        <stringProp name=\"Argument.name\">slots</stringProp>\n                      </elementProp>\n                    </collectionProp>\n                  </elementProp>\n                  <stringProp name=\"HTTPSampler.domain\">${host}</stringProp>\n                  <stringProp name=\"HTTPSampler.port\">${port}</stringProp>\n                  <stringProp name=\"HTTPSampler.protocol\">${protocol}</stringProp>\n                  <stringProp name=\"HTTPSampler.contentEncoding\">utf-8</stringProp>\n                  <stringProp name=\"HTTPSampler.path\">/mod/quiz/processattempt.php</stringProp>\n                  <stringProp name=\"HTTPSampler.method\">POST</stringProp>\n                  <boolProp name=\"HTTPSampler.follow_redirects\">true</boolProp>\n                  <boolProp name=\"HTTPSampler.auto_redirects\">false</boolProp>\n                  <boolProp name=\"HTTPSampler.use_keepalive\">true</boolProp>\n                  <boolProp name=\"HTTPSampler.DO_MULTIPART_POST\">true</boolProp>\n                  <stringProp name=\"HTTPSampler.embedded_url_re\"></stringProp>\n                  <stringProp name=\"HTTPSampler.implementation\">Java</stringProp>\n                  <stringProp name=\"HTTPSampler.connect_timeout\"></stringProp>\n                  <stringProp name=\"HTTPSampler.response_timeout\"></stringProp>\n                </HTTPSamplerProxy>\n                <hashTree>\n                  <ResponseAssertion guiclass=\"AssertionGui\" testclass=\"ResponseAssertion\" testname=\"Assertion - Response Contains Summary of attempt\" enabled=\"true\">\n                    <collectionProp name=\"Asserion.test_strings\">\n                      <stringProp name=\"-999752098\">Summary of attempt</stringProp>\n                    </collectionProp>\n                    <intProp name=\"Assertion.test_type\">2</intProp>\n                    <stringProp name=\"Assertion.test_field\">Assertion.response_data</stringProp>\n                    <boolProp name=\"Assertion.assume_success\">false</boolProp>\n                    <stringProp name=\"Assertion.custom_message\"></stringProp>\n                  </ResponseAssertion>\n                  <hashTree/>\n                  <GaussianRandomTimer guiclass=\"GaussianRandomTimerGui\" testclass=\"GaussianRandomTimer\" testname=\"Gaussian Random Timer\" enabled=\"true\">\n                    <stringProp name=\"ConstantTimer.delay\">2000</stringProp>\n                    <stringProp name=\"RandomTimer.range\">1000</stringProp>\n                  </GaussianRandomTimer>\n                  <hashTree/>\n                  <ConstantTimer guiclass=\"ConstantTimerGui\" testclass=\"ConstantTimer\" testname=\"Constant Timer\" enabled=\"false\">\n                    <stringProp name=\"ConstantTimer.delay\">1000</stringProp>\n                  </ConstantTimer>\n                  <hashTree/>\n                  <ResultCollector guiclass=\"ViewResultsFullVisualizer\" testclass=\"ResultCollector\" testname=\"View Results Tree\" enabled=\"false\">\n                    <boolProp name=\"ResultCollector.error_logging\">false</boolProp>\n                    <objProp>\n                      <name>saveConfig</name>\n                      <value class=\"SampleSaveConfiguration\">\n                        <time>true</time>\n                        <latency>true</latency>\n                        <timestamp>true</timestamp>\n                        <success>true</success>\n                        <label>true</label>\n                        <code>true</code>\n                        <message>true</message>\n                        <threadName>true</threadName>\n                        <dataType>true</dataType>\n                        <encoding>false</encoding>\n                        <assertions>true</assertions>\n                        <subresults>true</subresults>\n                        <responseData>false</responseData>\n                        <samplerData>false</samplerData>\n                        <xml>false</xml>\n                        <fieldNames>true</fieldNames>\n                        <responseHeaders>false</responseHeaders>\n                        <requestHeaders>false</requestHeaders>\n                        <responseDataOnError>false</responseDataOnError>\n                        <saveAssertionResultsFailureMessage>true</saveAssertionResultsFailureMessage>\n                        <assertionsResultsToSave>0</assertionsResultsToSave>\n                        <bytes>true</bytes>\n                        <sentBytes>true</sentBytes>\n                        <threadCounts>true</threadCounts>\n                        <idleTime>true</idleTime>\n                        <connectTime>true</connectTime>\n                      </value>\n                    </objProp>\n                    <stringProp name=\"filename\"></stringProp>\n                  </ResultCollector>\n                  <hashTree/>\n                </hashTree>\n                <HTTPSamplerProxy guiclass=\"HttpTestSampleGui\" testclass=\"HTTPSamplerProxy\" testname=\"Submit all and finish\" enabled=\"true\">\n                  <elementProp name=\"HTTPsampler.Arguments\" elementType=\"Arguments\" guiclass=\"HTTPArgumentsPanel\" testclass=\"Arguments\" testname=\"Argument List\" enabled=\"true\">\n                    <collectionProp name=\"Arguments.arguments\">\n                      <elementProp name=\"sesskey\" elementType=\"HTTPArgument\">\n                        <boolProp name=\"HTTPArgument.always_encode\">false</boolProp>\n                        <stringProp name=\"Argument.value\">${sesskey}</stringProp>\n                        <stringProp name=\"Argument.metadata\">=</stringProp>\n                        <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                        <stringProp name=\"Argument.name\">sesskey</stringProp>\n                      </elementProp>\n                      <elementProp name=\"slots\" elementType=\"HTTPArgument\">\n                        <boolProp name=\"HTTPArgument.always_encode\">false</boolProp>\n                        <stringProp name=\"Argument.value\"></stringProp>\n                        <stringProp name=\"Argument.metadata\">=</stringProp>\n                        <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                        <stringProp name=\"Argument.name\">slots</stringProp>\n                      </elementProp>\n                      <elementProp name=\"timeup\" elementType=\"HTTPArgument\">\n                        <boolProp name=\"HTTPArgument.always_encode\">false</boolProp>\n                        <stringProp name=\"Argument.value\">0</stringProp>\n                        <stringProp name=\"Argument.metadata\">=</stringProp>\n                        <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                        <stringProp name=\"Argument.name\">timeup</stringProp>\n                      </elementProp>\n                      <elementProp name=\"finishattempt\" elementType=\"HTTPArgument\">\n                        <boolProp name=\"HTTPArgument.always_encode\">false</boolProp>\n                        <stringProp name=\"Argument.value\">1</stringProp>\n                        <stringProp name=\"Argument.metadata\">=</stringProp>\n                        <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                        <stringProp name=\"Argument.name\">finishattempt</stringProp>\n                      </elementProp>\n                      <elementProp name=\"attempt\" elementType=\"HTTPArgument\">\n                        <boolProp name=\"HTTPArgument.always_encode\">false</boolProp>\n                        <stringProp name=\"Argument.value\">${attemptid}</stringProp>\n                        <stringProp name=\"Argument.metadata\">=</stringProp>\n                        <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                        <stringProp name=\"Argument.name\">attempt</stringProp>\n                      </elementProp>\n                    </collectionProp>\n                  </elementProp>\n                  <stringProp name=\"HTTPSampler.domain\">${host}</stringProp>\n                  <stringProp name=\"HTTPSampler.port\">${port}</stringProp>\n                  <stringProp name=\"HTTPSampler.protocol\">${protocol}</stringProp>\n                  <stringProp name=\"HTTPSampler.contentEncoding\">utf-8</stringProp>\n                  <stringProp name=\"HTTPSampler.path\">/mod/quiz/processattempt.php</stringProp>\n                  <stringProp name=\"HTTPSampler.method\">POST</stringProp>\n                  <boolProp name=\"HTTPSampler.follow_redirects\">true</boolProp>\n                  <boolProp name=\"HTTPSampler.auto_redirects\">false</boolProp>\n                  <boolProp name=\"HTTPSampler.use_keepalive\">true</boolProp>\n                  <boolProp name=\"HTTPSampler.DO_MULTIPART_POST\">true</boolProp>\n                  <stringProp name=\"HTTPSampler.embedded_url_re\"></stringProp>\n                  <stringProp name=\"HTTPSampler.implementation\">Java</stringProp>\n                  <stringProp name=\"HTTPSampler.connect_timeout\"></stringProp>\n                  <stringProp name=\"HTTPSampler.response_timeout\"></stringProp>\n                </HTTPSamplerProxy>\n                <hashTree>\n                  <ResponseAssertion guiclass=\"AssertionGui\" testclass=\"ResponseAssertion\" testname=\"Assertion - Response Contains Finish review\" enabled=\"true\">\n                    <collectionProp name=\"Asserion.test_strings\">\n                      <stringProp name=\"-1084012699\">Finish review</stringProp>\n                    </collectionProp>\n                    <intProp name=\"Assertion.test_type\">2</intProp>\n                    <stringProp name=\"Assertion.test_field\">Assertion.response_data</stringProp>\n                    <boolProp name=\"Assertion.assume_success\">false</boolProp>\n                    <stringProp name=\"Assertion.custom_message\"></stringProp>\n                  </ResponseAssertion>\n                  <hashTree/>\n                  <GaussianRandomTimer guiclass=\"GaussianRandomTimerGui\" testclass=\"GaussianRandomTimer\" testname=\"Gaussian Random Timer\" enabled=\"true\">\n                    <stringProp name=\"ConstantTimer.delay\">2000</stringProp>\n                    <stringProp name=\"RandomTimer.range\">1000</stringProp>\n                  </GaussianRandomTimer>\n                  <hashTree/>\n                  <ConstantTimer guiclass=\"ConstantTimerGui\" testclass=\"ConstantTimer\" testname=\"Constant Timer\" enabled=\"false\">\n                    <stringProp name=\"ConstantTimer.delay\">1000</stringProp>\n                  </ConstantTimer>\n                  <hashTree/>\n                  <ResultCollector guiclass=\"ViewResultsFullVisualizer\" testclass=\"ResultCollector\" testname=\"View Results Tree\" enabled=\"false\">\n                    <boolProp name=\"ResultCollector.error_logging\">false</boolProp>\n                    <objProp>\n                      <name>saveConfig</name>\n                      <value class=\"SampleSaveConfiguration\">\n                        <time>true</time>\n                        <latency>true</latency>\n                        <timestamp>true</timestamp>\n                        <success>true</success>\n                        <label>true</label>\n                        <code>true</code>\n                        <message>true</message>\n                        <threadName>true</threadName>\n                        <dataType>true</dataType>\n                        <encoding>false</encoding>\n                        <assertions>true</assertions>\n                        <subresults>true</subresults>\n                        <responseData>false</responseData>\n                        <samplerData>false</samplerData>\n                        <xml>false</xml>\n                        <fieldNames>true</fieldNames>\n                        <responseHeaders>false</responseHeaders>\n                        <requestHeaders>false</requestHeaders>\n                        <responseDataOnError>false</responseDataOnError>\n                        <saveAssertionResultsFailureMessage>true</saveAssertionResultsFailureMessage>\n                        <assertionsResultsToSave>0</assertionsResultsToSave>\n                        <bytes>true</bytes>\n                        <sentBytes>true</sentBytes>\n                        <threadCounts>true</threadCounts>\n                        <idleTime>true</idleTime>\n                        <connectTime>true</connectTime>\n                      </value>\n                    </objProp>\n                    <stringProp name=\"filename\"></stringProp>\n                  </ResultCollector>\n                  <hashTree/>\n                </hashTree>\n                <HTTPSamplerProxy guiclass=\"HttpTestSampleGui\" testclass=\"HTTPSamplerProxy\" testname=\"Click Finish Review\" enabled=\"true\">\n                  <elementProp name=\"HTTPsampler.Arguments\" elementType=\"Arguments\" guiclass=\"HTTPArgumentsPanel\" testclass=\"Arguments\" testname=\"Argument List\" enabled=\"true\">\n                    <collectionProp name=\"Arguments.arguments\">\n                      <elementProp name=\"id\" elementType=\"HTTPArgument\">\n                        <boolProp name=\"HTTPArgument.always_encode\">false</boolProp>\n                        <stringProp name=\"Argument.value\">${moduleid}</stringProp>\n                        <stringProp name=\"Argument.metadata\">=</stringProp>\n                        <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                        <stringProp name=\"Argument.name\">id</stringProp>\n                      </elementProp>\n                    </collectionProp>\n                  </elementProp>\n                  <stringProp name=\"HTTPSampler.domain\">${host}</stringProp>\n                  <stringProp name=\"HTTPSampler.port\">${port}</stringProp>\n                  <stringProp name=\"HTTPSampler.protocol\">${protocol}</stringProp>\n                  <stringProp name=\"HTTPSampler.contentEncoding\"></stringProp>\n                  <stringProp name=\"HTTPSampler.path\">/mod/quiz/view.php</stringProp>\n                  <stringProp name=\"HTTPSampler.method\">GET</stringProp>\n                  <boolProp name=\"HTTPSampler.follow_redirects\">true</boolProp>\n                  <boolProp name=\"HTTPSampler.auto_redirects\">false</boolProp>\n                  <boolProp name=\"HTTPSampler.use_keepalive\">true</boolProp>\n                  <boolProp name=\"HTTPSampler.DO_MULTIPART_POST\">true</boolProp>\n                  <stringProp name=\"HTTPSampler.embedded_url_re\"></stringProp>\n                  <stringProp name=\"HTTPSampler.implementation\">Java</stringProp>\n                  <stringProp name=\"HTTPSampler.connect_timeout\"></stringProp>\n                  <stringProp name=\"HTTPSampler.response_timeout\"></stringProp>\n                </HTTPSamplerProxy>\n                <hashTree>\n                  <ResponseAssertion guiclass=\"AssertionGui\" testclass=\"ResponseAssertion\" testname=\"Assertion - Response Contains Summary of your previous atte...\" enabled=\"true\">\n                    <collectionProp name=\"Asserion.test_strings\">\n                      <stringProp name=\"1813422065\">Summary of your previous attempts</stringProp>\n                    </collectionProp>\n                    <intProp name=\"Assertion.test_type\">2</intProp>\n                    <stringProp name=\"Assertion.test_field\">Assertion.response_data</stringProp>\n                    <boolProp name=\"Assertion.assume_success\">false</boolProp>\n                    <stringProp name=\"Assertion.custom_message\"></stringProp>\n                  </ResponseAssertion>\n                  <hashTree/>\n                  <ResponseAssertion guiclass=\"AssertionGui\" testclass=\"ResponseAssertion\" testname=\"Assertion - Response Contains Highest grade\" enabled=\"true\">\n                    <collectionProp name=\"Asserion.test_strings\">\n                      <stringProp name=\"-1064949445\">Highest grade</stringProp>\n                    </collectionProp>\n                    <intProp name=\"Assertion.test_type\">2</intProp>\n                    <stringProp name=\"Assertion.test_field\">Assertion.response_data</stringProp>\n                    <boolProp name=\"Assertion.assume_success\">false</boolProp>\n                    <stringProp name=\"Assertion.custom_message\"></stringProp>\n                  </ResponseAssertion>\n                  <hashTree/>\n                  <GaussianRandomTimer guiclass=\"GaussianRandomTimerGui\" testclass=\"GaussianRandomTimer\" testname=\"Gaussian Random Timer\" enabled=\"true\">\n                    <stringProp name=\"ConstantTimer.delay\">4000</stringProp>\n                    <stringProp name=\"RandomTimer.range\">3000</stringProp>\n                  </GaussianRandomTimer>\n                  <hashTree/>\n                  <ConstantTimer guiclass=\"ConstantTimerGui\" testclass=\"ConstantTimer\" testname=\"Constant Timer\" enabled=\"false\">\n                    <stringProp name=\"ConstantTimer.delay\">1000</stringProp>\n                  </ConstantTimer>\n                  <hashTree/>\n                </hashTree>\n                <DebugSampler guiclass=\"TestBeanGUI\" testclass=\"DebugSampler\" testname=\"Debug Sampler\" enabled=\"false\">\n                  <boolProp name=\"displayJMeterProperties\">false</boolProp>\n                  <boolProp name=\"displayJMeterVariables\">true</boolProp>\n                  <boolProp name=\"displaySystemProperties\">false</boolProp>\n                </DebugSampler>\n                <hashTree/>\n                <ResultCollector guiclass=\"GraphVisualizer\" testclass=\"ResultCollector\" testname=\"Graph Results\" enabled=\"false\">\n                  <boolProp name=\"ResultCollector.error_logging\">false</boolProp>\n                  <objProp>\n                    <name>saveConfig</name>\n                    <value class=\"SampleSaveConfiguration\">\n                      <time>true</time>\n                      <latency>true</latency>\n                      <timestamp>true</timestamp>\n                      <success>true</success>\n                      <label>true</label>\n                      <code>true</code>\n                      <message>true</message>\n                      <threadName>true</threadName>\n                      <dataType>true</dataType>\n                      <encoding>false</encoding>\n                      <assertions>true</assertions>\n                      <subresults>true</subresults>\n                      <responseData>false</responseData>\n                      <samplerData>false</samplerData>\n                      <xml>true</xml>\n                      <fieldNames>false</fieldNames>\n                      <responseHeaders>false</responseHeaders>\n                      <requestHeaders>false</requestHeaders>\n                      <responseDataOnError>false</responseDataOnError>\n                      <saveAssertionResultsFailureMessage>false</saveAssertionResultsFailureMessage>\n                      <assertionsResultsToSave>0</assertionsResultsToSave>\n                      <bytes>true</bytes>\n                      <threadCounts>true</threadCounts>\n                    </value>\n                  </objProp>\n                  <stringProp name=\"filename\"></stringProp>\n                </ResultCollector>\n                <hashTree/>\n                <ResultCollector guiclass=\"ViewResultsFullVisualizer\" testclass=\"ResultCollector\" testname=\"View Results Tree\" enabled=\"false\">\n                  <boolProp name=\"ResultCollector.error_logging\">false</boolProp>\n                  <objProp>\n                    <name>saveConfig</name>\n                    <value class=\"SampleSaveConfiguration\">\n                      <time>true</time>\n                      <latency>true</latency>\n                      <timestamp>true</timestamp>\n                      <success>true</success>\n                      <label>true</label>\n                      <code>true</code>\n                      <message>true</message>\n                      <threadName>true</threadName>\n                      <dataType>true</dataType>\n                      <encoding>false</encoding>\n                      <assertions>true</assertions>\n                      <subresults>true</subresults>\n                      <responseData>false</responseData>\n                      <samplerData>false</samplerData>\n                      <xml>false</xml>\n                      <fieldNames>false</fieldNames>\n                      <responseHeaders>false</responseHeaders>\n                      <requestHeaders>false</requestHeaders>\n                      <responseDataOnError>false</responseDataOnError>\n                      <saveAssertionResultsFailureMessage>false</saveAssertionResultsFailureMessage>\n                      <assertionsResultsToSave>0</assertionsResultsToSave>\n                      <bytes>true</bytes>\n                      <threadCounts>true</threadCounts>\n                    </value>\n                  </objProp>\n                  <stringProp name=\"filename\"></stringProp>\n                </ResultCollector>\n                <hashTree/>\n                <GaussianRandomTimer guiclass=\"GaussianRandomTimerGui\" testclass=\"GaussianRandomTimer\" testname=\"Gaussian Random Timer\" enabled=\"true\">\n                  <stringProp name=\"ConstantTimer.delay\">2000</stringProp>\n                  <stringProp name=\"RandomTimer.range\">1000</stringProp>\n                </GaussianRandomTimer>\n                <hashTree/>\n              </hashTree>\n              <ResultCollector guiclass=\"ViewResultsFullVisualizer\" testclass=\"ResultCollector\" testname=\"View Results Tree\" enabled=\"true\">\n                <boolProp name=\"ResultCollector.error_logging\">false</boolProp>\n                <objProp>\n                  <name>saveConfig</name>\n                  <value class=\"SampleSaveConfiguration\">\n                    <time>true</time>\n                    <latency>true</latency>\n                    <timestamp>true</timestamp>\n                    <success>true</success>\n                    <label>true</label>\n                    <code>true</code>\n                    <message>true</message>\n                    <threadName>true</threadName>\n                    <dataType>true</dataType>\n                    <encoding>false</encoding>\n                    <assertions>true</assertions>\n                    <subresults>true</subresults>\n                    <responseData>false</responseData>\n                    <samplerData>false</samplerData>\n                    <xml>false</xml>\n                    <fieldNames>true</fieldNames>\n                    <responseHeaders>false</responseHeaders>\n                    <requestHeaders>false</requestHeaders>\n                    <responseDataOnError>false</responseDataOnError>\n                    <saveAssertionResultsFailureMessage>true</saveAssertionResultsFailureMessage>\n                    <assertionsResultsToSave>0</assertionsResultsToSave>\n                    <bytes>true</bytes>\n                    <sentBytes>true</sentBytes>\n                    <threadCounts>true</threadCounts>\n                    <idleTime>true</idleTime>\n                    <connectTime>true</connectTime>\n                  </value>\n                </objProp>\n                <stringProp name=\"filename\"></stringProp>\n              </ResultCollector>\n              <hashTree/>\n              <ResultCollector guiclass=\"TableVisualizer\" testclass=\"ResultCollector\" testname=\"View Results in Table\" enabled=\"true\">\n                <boolProp name=\"ResultCollector.error_logging\">false</boolProp>\n                <objProp>\n                  <name>saveConfig</name>\n                  <value class=\"SampleSaveConfiguration\">\n                    <time>true</time>\n                    <latency>true</latency>\n                    <timestamp>true</timestamp>\n                    <success>true</success>\n                    <label>true</label>\n                    <code>true</code>\n                    <message>true</message>\n                    <threadName>true</threadName>\n                    <dataType>true</dataType>\n                    <encoding>false</encoding>\n                    <assertions>true</assertions>\n                    <subresults>true</subresults>\n                    <responseData>false</responseData>\n                    <samplerData>false</samplerData>\n                    <xml>false</xml>\n                    <fieldNames>true</fieldNames>\n                    <responseHeaders>false</responseHeaders>\n                    <requestHeaders>false</requestHeaders>\n                    <responseDataOnError>false</responseDataOnError>\n                    <saveAssertionResultsFailureMessage>true</saveAssertionResultsFailureMessage>\n                    <assertionsResultsToSave>0</assertionsResultsToSave>\n                    <bytes>true</bytes>\n                    <sentBytes>true</sentBytes>\n                    <threadCounts>true</threadCounts>\n                    <idleTime>true</idleTime>\n                    <connectTime>true</connectTime>\n                  </value>\n                </objProp>\n                <stringProp name=\"filename\"></stringProp>\n              </ResultCollector>\n              <hashTree/>\n              <ResultCollector guiclass=\"SummaryReport\" testclass=\"ResultCollector\" testname=\"Summary Report\" enabled=\"true\">\n                <boolProp name=\"ResultCollector.error_logging\">false</boolProp>\n                <objProp>\n                  <name>saveConfig</name>\n                  <value class=\"SampleSaveConfiguration\">\n                    <time>true</time>\n                    <latency>true</latency>\n                    <timestamp>true</timestamp>\n                    <success>true</success>\n                    <label>true</label>\n                    <code>true</code>\n                    <message>true</message>\n                    <threadName>true</threadName>\n                    <dataType>true</dataType>\n                    <encoding>false</encoding>\n                    <assertions>true</assertions>\n                    <subresults>true</subresults>\n                    <responseData>false</responseData>\n                    <samplerData>false</samplerData>\n                    <xml>false</xml>\n                    <fieldNames>true</fieldNames>\n                    <responseHeaders>false</responseHeaders>\n                    <requestHeaders>false</requestHeaders>\n                    <responseDataOnError>false</responseDataOnError>\n                    <saveAssertionResultsFailureMessage>true</saveAssertionResultsFailureMessage>\n                    <assertionsResultsToSave>0</assertionsResultsToSave>\n                    <bytes>true</bytes>\n                    <sentBytes>true</sentBytes>\n                    <threadCounts>true</threadCounts>\n                    <idleTime>true</idleTime>\n                    <connectTime>true</connectTime>\n                  </value>\n                </objProp>\n                <stringProp name=\"filename\"></stringProp>\n              </ResultCollector>\n              <hashTree/>\n              <kg.apc.jmeter.vizualizers.CorrectedResultCollector guiclass=\"kg.apc.jmeter.vizualizers.HitsPerSecondGui\" testclass=\"kg.apc.jmeter.vizualizers.CorrectedResultCollector\" testname=\"jp@gc - Hits per Second\" enabled=\"true\">\n                <boolProp name=\"ResultCollector.error_logging\">false</boolProp>\n                <objProp>\n                  <name>saveConfig</name>\n                  <value class=\"SampleSaveConfiguration\">\n                    <time>true</time>\n                    <latency>true</latency>\n                    <timestamp>true</timestamp>\n                    <success>true</success>\n                    <label>true</label>\n                    <code>true</code>\n                    <message>true</message>\n                    <threadName>true</threadName>\n                    <dataType>true</dataType>\n                    <encoding>false</encoding>\n                    <assertions>true</assertions>\n                    <subresults>true</subresults>\n                    <responseData>false</responseData>\n                    <samplerData>false</samplerData>\n                    <xml>false</xml>\n                    <fieldNames>true</fieldNames>\n                    <responseHeaders>false</responseHeaders>\n                    <requestHeaders>false</requestHeaders>\n                    <responseDataOnError>false</responseDataOnError>\n                    <saveAssertionResultsFailureMessage>true</saveAssertionResultsFailureMessage>\n                    <assertionsResultsToSave>0</assertionsResultsToSave>\n                    <bytes>true</bytes>\n                    <sentBytes>true</sentBytes>\n                    <threadCounts>true</threadCounts>\n                    <idleTime>true</idleTime>\n                    <connectTime>true</connectTime>\n                  </value>\n                </objProp>\n                <stringProp name=\"filename\"></stringProp>\n                <longProp name=\"interval_grouping\">1000</longProp>\n                <boolProp name=\"graph_aggregated\">false</boolProp>\n                <stringProp name=\"include_sample_labels\"></stringProp>\n                <stringProp name=\"exclude_sample_labels\"></stringProp>\n                <stringProp name=\"start_offset\"></stringProp>\n                <stringProp name=\"end_offset\"></stringProp>\n                <boolProp name=\"include_checkbox_state\">false</boolProp>\n                <boolProp name=\"exclude_checkbox_state\">false</boolProp>\n              </kg.apc.jmeter.vizualizers.CorrectedResultCollector>\n              <hashTree/>\n              <kg.apc.jmeter.vizualizers.CorrectedResultCollector guiclass=\"kg.apc.jmeter.vizualizers.ResponseCodesPerSecondGui\" testclass=\"kg.apc.jmeter.vizualizers.CorrectedResultCollector\" testname=\"jp@gc - Response Codes per Second\" enabled=\"true\">\n                <boolProp name=\"ResultCollector.error_logging\">false</boolProp>\n                <objProp>\n                  <name>saveConfig</name>\n                  <value class=\"SampleSaveConfiguration\">\n                    <time>true</time>\n                    <latency>true</latency>\n                    <timestamp>true</timestamp>\n                    <success>true</success>\n                    <label>true</label>\n                    <code>true</code>\n                    <message>true</message>\n                    <threadName>true</threadName>\n                    <dataType>true</dataType>\n                    <encoding>false</encoding>\n                    <assertions>true</assertions>\n                    <subresults>true</subresults>\n                    <responseData>false</responseData>\n                    <samplerData>false</samplerData>\n                    <xml>false</xml>\n                    <fieldNames>true</fieldNames>\n                    <responseHeaders>false</responseHeaders>\n                    <requestHeaders>false</requestHeaders>\n                    <responseDataOnError>false</responseDataOnError>\n                    <saveAssertionResultsFailureMessage>true</saveAssertionResultsFailureMessage>\n                    <assertionsResultsToSave>0</assertionsResultsToSave>\n                    <bytes>true</bytes>\n                    <sentBytes>true</sentBytes>\n                    <threadCounts>true</threadCounts>\n                    <idleTime>true</idleTime>\n                    <connectTime>true</connectTime>\n                  </value>\n                </objProp>\n                <stringProp name=\"filename\"></stringProp>\n                <longProp name=\"interval_grouping\">1000</longProp>\n                <boolProp name=\"graph_aggregated\">false</boolProp>\n                <stringProp name=\"include_sample_labels\"></stringProp>\n                <stringProp name=\"exclude_sample_labels\"></stringProp>\n                <stringProp name=\"start_offset\"></stringProp>\n                <stringProp name=\"end_offset\"></stringProp>\n                <boolProp name=\"include_checkbox_state\">false</boolProp>\n                <boolProp name=\"exclude_checkbox_state\">false</boolProp>\n              </kg.apc.jmeter.vizualizers.CorrectedResultCollector>\n              <hashTree/>\n              <kg.apc.jmeter.vizualizers.CorrectedResultCollector guiclass=\"kg.apc.jmeter.vizualizers.LatenciesOverTimeGui\" testclass=\"kg.apc.jmeter.vizualizers.CorrectedResultCollector\" testname=\"jp@gc - Response Latencies Over Time\" enabled=\"true\">\n                <boolProp name=\"ResultCollector.error_logging\">false</boolProp>\n                <objProp>\n                  <name>saveConfig</name>\n                  <value class=\"SampleSaveConfiguration\">\n                    <time>true</time>\n                    <latency>true</latency>\n                    <timestamp>true</timestamp>\n                    <success>true</success>\n                    <label>true</label>\n                    <code>true</code>\n                    <message>true</message>\n                    <threadName>true</threadName>\n                    <dataType>true</dataType>\n                    <encoding>false</encoding>\n                    <assertions>true</assertions>\n                    <subresults>true</subresults>\n                    <responseData>false</responseData>\n                    <samplerData>false</samplerData>\n                    <xml>false</xml>\n                    <fieldNames>true</fieldNames>\n                    <responseHeaders>false</responseHeaders>\n                    <requestHeaders>false</requestHeaders>\n                    <responseDataOnError>false</responseDataOnError>\n                    <saveAssertionResultsFailureMessage>true</saveAssertionResultsFailureMessage>\n                    <assertionsResultsToSave>0</assertionsResultsToSave>\n                    <bytes>true</bytes>\n                    <sentBytes>true</sentBytes>\n                    <threadCounts>true</threadCounts>\n                    <idleTime>true</idleTime>\n                    <connectTime>true</connectTime>\n                  </value>\n                </objProp>\n                <stringProp name=\"filename\"></stringProp>\n                <longProp name=\"interval_grouping\">500</longProp>\n                <boolProp name=\"graph_aggregated\">false</boolProp>\n                <stringProp name=\"include_sample_labels\"></stringProp>\n                <stringProp name=\"exclude_sample_labels\"></stringProp>\n                <stringProp name=\"start_offset\"></stringProp>\n                <stringProp name=\"end_offset\"></stringProp>\n                <boolProp name=\"include_checkbox_state\">false</boolProp>\n                <boolProp name=\"exclude_checkbox_state\">false</boolProp>\n              </kg.apc.jmeter.vizualizers.CorrectedResultCollector>\n              <hashTree/>\n            </hashTree>\n            <ResultCollector guiclass=\"StatVisualizer\" testclass=\"ResultCollector\" testname=\"Aggregate Report\" enabled=\"true\">\n              <boolProp name=\"ResultCollector.error_logging\">false</boolProp>\n              <objProp>\n                <name>saveConfig</name>\n                <value class=\"SampleSaveConfiguration\">\n                  <time>true</time>\n                  <latency>true</latency>\n                  <timestamp>true</timestamp>\n                  <success>true</success>\n                  <label>true</label>\n                  <code>true</code>\n                  <message>true</message>\n                  <threadName>true</threadName>\n                  <dataType>true</dataType>\n                  <encoding>false</encoding>\n                  <assertions>true</assertions>\n                  <subresults>true</subresults>\n                  <responseData>false</responseData>\n                  <samplerData>false</samplerData>\n                  <xml>false</xml>\n                  <fieldNames>true</fieldNames>\n                  <responseHeaders>false</responseHeaders>\n                  <requestHeaders>false</requestHeaders>\n                  <responseDataOnError>false</responseDataOnError>\n                  <saveAssertionResultsFailureMessage>true</saveAssertionResultsFailureMessage>\n                  <assertionsResultsToSave>0</assertionsResultsToSave>\n                  <bytes>true</bytes>\n                  <sentBytes>true</sentBytes>\n                  <threadCounts>true</threadCounts>\n                  <idleTime>true</idleTime>\n                  <connectTime>true</connectTime>\n                </value>\n              </objProp>\n              <stringProp name=\"filename\"></stringProp>\n            </ResultCollector>\n            <hashTree/>\n            <ResultCollector guiclass=\"ViewResultsFullVisualizer\" testclass=\"ResultCollector\" testname=\"View Results Tree\" enabled=\"true\">\n              <boolProp name=\"ResultCollector.error_logging\">false</boolProp>\n              <objProp>\n                <name>saveConfig</name>\n                <value class=\"SampleSaveConfiguration\">\n                  <time>true</time>\n                  <latency>true</latency>\n                  <timestamp>true</timestamp>\n                  <success>true</success>\n                  <label>true</label>\n                  <code>true</code>\n                  <message>true</message>\n                  <threadName>true</threadName>\n                  <dataType>true</dataType>\n                  <encoding>false</encoding>\n                  <assertions>true</assertions>\n                  <subresults>true</subresults>\n                  <responseData>false</responseData>\n                  <samplerData>false</samplerData>\n                  <xml>false</xml>\n                  <fieldNames>true</fieldNames>\n                  <responseHeaders>false</responseHeaders>\n                  <requestHeaders>false</requestHeaders>\n                  <responseDataOnError>false</responseDataOnError>\n                  <saveAssertionResultsFailureMessage>true</saveAssertionResultsFailureMessage>\n                  <assertionsResultsToSave>0</assertionsResultsToSave>\n                  <bytes>true</bytes>\n                  <sentBytes>true</sentBytes>\n                  <threadCounts>true</threadCounts>\n                  <idleTime>true</idleTime>\n                  <connectTime>true</connectTime>\n                </value>\n              </objProp>\n              <stringProp name=\"filename\"></stringProp>\n            </ResultCollector>\n            <hashTree/>\n          </hashTree>\n        </hashTree>\n      </hashTree>\n    </hashTree>\n  </hashTree>\n</jmeterTestPlan>\n"
  },
  {
    "path": "loadtest/time-gated-exam-test.jmx",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<jmeterTestPlan version=\"1.2\" properties=\"4.0\" jmeter=\"4.0 r1823414\">\n  <hashTree>\n    <TestPlan guiclass=\"TestPlanGui\" testclass=\"TestPlan\" testname=\"Moodle Load Test Plan\" enabled=\"true\">\n      <boolProp name=\"TestPlan.functional_mode\">false</boolProp>\n      <collectionProp name=\"TestPlan.thread_groups\"/>\n      <elementProp name=\"TestPlan.user_defined_variables\" elementType=\"Arguments\" guiclass=\"ArgumentsPanel\" testclass=\"Arguments\" testname=\"Argument List\" enabled=\"true\">\n        <collectionProp name=\"Arguments.arguments\"/>\n      </elementProp>\n      <boolProp name=\"TestPlan.serialize_threadgroups\">false</boolProp>\n      <stringProp name=\"TestPlan.comments\"></stringProp>\n      <stringProp name=\"TestPlan.user_define_classpath\"></stringProp>\n    </TestPlan>\n    <hashTree>\n      <Arguments guiclass=\"ArgumentsPanel\" testclass=\"Arguments\" testname=\"User Defined Variables\" enabled=\"true\">\n        <collectionProp name=\"Arguments.arguments\">\n          <elementProp name=\"login_host\" elementType=\"Argument\">\n            <stringProp name=\"Argument.name\">login_host</stringProp>\n            <stringProp name=\"Argument.value\">${__P(host, lb-d5t45x.westeurope.cloudapp.azure.com)}</stringProp>\n            <stringProp name=\"Argument.metadata\">=</stringProp>\n          </elementProp>\n          <elementProp name=\"host\" elementType=\"Argument\">\n            <stringProp name=\"Argument.name\">host</stringProp>\n            <stringProp name=\"Argument.value\">${__P(host, lb-d5t45x.westeurope.cloudapp.azure.com)}</stringProp>\n            <stringProp name=\"Argument.metadata\">=</stringProp>\n          </elementProp>\n          <elementProp name=\"threads\" elementType=\"Argument\">\n            <stringProp name=\"Argument.name\">threads</stringProp>\n            <stringProp name=\"Argument.value\">${__P(threads,1000)}</stringProp>\n            <stringProp name=\"Argument.metadata\">=</stringProp>\n            <stringProp name=\"Argument.desc\">Default 10 (for a quick GUI run)</stringProp>\n          </elementProp>\n          <elementProp name=\"rampup\" elementType=\"Argument\">\n            <stringProp name=\"Argument.name\">rampup</stringProp>\n            <stringProp name=\"Argument.value\">${__P(rampup,1000)}</stringProp>\n            <stringProp name=\"Argument.metadata\">=</stringProp>\n            <stringProp name=\"Argument.desc\">Default 120 - 1/15 runtime default rampup</stringProp>\n          </elementProp>\n          <elementProp name=\"runtime\" elementType=\"Argument\">\n            <stringProp name=\"Argument.name\">runtime</stringProp>\n            <stringProp name=\"Argument.value\">${__P(runtime,3600)}</stringProp>\n            <stringProp name=\"Argument.metadata\">=</stringProp>\n            <stringProp name=\"Argument.desc\">Default 1800 = 30 minutes = 30*60 seconds</stringProp>\n          </elementProp>\n          <elementProp name=\"default_connect_timeout\" elementType=\"Argument\">\n            <stringProp name=\"Argument.name\">default_connect_timeout</stringProp>\n            <stringProp name=\"Argument.value\">2000</stringProp>\n            <stringProp name=\"Argument.metadata\">=</stringProp>\n            <stringProp name=\"Argument.desc\">1.2 seconds to a TCP ack</stringProp>\n          </elementProp>\n          <elementProp name=\"default_response_timeout\" elementType=\"Argument\">\n            <stringProp name=\"Argument.name\">default_response_timeout</stringProp>\n            <stringProp name=\"Argument.value\">240000</stringProp>\n            <stringProp name=\"Argument.metadata\">=</stringProp>\n            <stringProp name=\"Argument.desc\">4 Minutes for the page to respond</stringProp>\n          </elementProp>\n          <elementProp name=\"throughput_rate\" elementType=\"Argument\">\n            <stringProp name=\"Argument.name\">throughput_rate</stringProp>\n            <stringProp name=\"Argument.value\">${__P(throughput_rate, 50)}</stringProp>\n            <stringProp name=\"Argument.metadata\">=</stringProp>\n            <stringProp name=\"Argument.desc\">Default 650 should approximate ~ 7k page views / 5 mins cacti sample</stringProp>\n          </elementProp>\n          <elementProp name=\"activity_course_id\" elementType=\"Argument\">\n            <stringProp name=\"Argument.name\">activity_course_id</stringProp>\n            <stringProp name=\"Argument.value\">${__P(activity_course_id, 2)}</stringProp>\n            <stringProp name=\"Argument.metadata\">=</stringProp>\n            <stringProp name=\"Argument.desc\">This must match the course ID of the course with performance activities</stringProp>\n          </elementProp>\n          <elementProp name=\"quiz_delay\" elementType=\"Argument\">\n            <stringProp name=\"Argument.name\">quiz_delay</stringProp>\n            <stringProp name=\"Argument.value\">${__P(quiz_delay, 100)}</stringProp>\n            <stringProp name=\"Argument.desc\">-1 means don&apos;t run quiz</stringProp>\n            <stringProp name=\"Argument.metadata\">=</stringProp>\n          </elementProp>\n          <elementProp name=\"quiz_users\" elementType=\"Argument\">\n            <stringProp name=\"Argument.name\">quiz_users</stringProp>\n            <stringProp name=\"Argument.value\">${__P(quiz_users, 1)}</stringProp>\n            <stringProp name=\"Argument.desc\">Number of quiz users to simulate</stringProp>\n            <stringProp name=\"Argument.metadata\">=</stringProp>\n          </elementProp>\n          <elementProp name=\"groupselect_delay\" elementType=\"Argument\">\n            <stringProp name=\"Argument.name\">groupselect_delay</stringProp>\n            <stringProp name=\"Argument.value\">${__P(groupselect_delay,-1)}</stringProp>\n            <stringProp name=\"Argument.metadata\">=</stringProp>\n          </elementProp>\n          <elementProp name=\"groupselect_users\" elementType=\"Argument\">\n            <stringProp name=\"Argument.name\">groupselect_users</stringProp>\n            <stringProp name=\"Argument.value\">${__P(groupselect_users, 0)}</stringProp>\n            <stringProp name=\"Argument.metadata\">=</stringProp>\n          </elementProp>\n          <elementProp name=\"protocol\" elementType=\"Argument\">\n            <stringProp name=\"Argument.name\">protocol</stringProp>\n            <stringProp name=\"Argument.value\">https</stringProp>\n            <stringProp name=\"Argument.metadata\">=</stringProp>\n          </elementProp>\n          <elementProp name=\"port\" elementType=\"Argument\">\n            <stringProp name=\"Argument.name\">port</stringProp>\n            <stringProp name=\"Argument.value\">443</stringProp>\n            <stringProp name=\"Argument.metadata\">=</stringProp>\n          </elementProp>\n          <elementProp name=\"moodle_user_pass\" elementType=\"Argument\">\n            <stringProp name=\"Argument.name\">moodle_user_pass</stringProp>\n            <stringProp name=\"Argument.value\">${__P(moodle_user_pass,testUserP@$$w0rd)}</stringProp>\n            <stringProp name=\"Argument.metadata\">=</stringProp>\n          </elementProp>\n        </collectionProp>\n      </Arguments>\n      <hashTree/>\n      <ThreadGroup guiclass=\"ThreadGroupGui\" testclass=\"ThreadGroup\" testname=\"Load Test Moodle\" enabled=\"true\">\n        <stringProp name=\"ThreadGroup.on_sample_error\">startnextloop</stringProp>\n        <elementProp name=\"ThreadGroup.main_controller\" elementType=\"LoopController\" guiclass=\"LoopControlPanel\" testclass=\"LoopController\" testname=\"Loop Controller\" enabled=\"true\">\n          <boolProp name=\"LoopController.continue_forever\">false</boolProp>\n          <stringProp name=\"LoopController.loops\">1</stringProp>\n        </elementProp>\n        <stringProp name=\"ThreadGroup.num_threads\">${threads}</stringProp>\n        <stringProp name=\"ThreadGroup.ramp_time\">${rampup}</stringProp>\n        <longProp name=\"ThreadGroup.start_time\">1513564238000</longProp>\n        <longProp name=\"ThreadGroup.end_time\">1513564538000</longProp>\n        <boolProp name=\"ThreadGroup.scheduler\">true</boolProp>\n        <stringProp name=\"ThreadGroup.duration\">${runtime}</stringProp>\n        <stringProp name=\"ThreadGroup.delay\">0</stringProp>\n      </ThreadGroup>\n      <hashTree>\n        <CookieManager guiclass=\"CookiePanel\" testclass=\"CookieManager\" testname=\"HTTP Cookie Manager\" enabled=\"true\">\n          <collectionProp name=\"CookieManager.cookies\"/>\n          <boolProp name=\"CookieManager.clearEachIteration\">true</boolProp>\n          <stringProp name=\"CookieManager.policy\">default</stringProp>\n        </CookieManager>\n        <hashTree/>\n        <GenericController guiclass=\"LogicControllerGui\" testclass=\"GenericController\" testname=\"Moodle Tests\" enabled=\"true\"/>\n        <hashTree>\n          <HeaderManager guiclass=\"HeaderPanel\" testclass=\"HeaderManager\" testname=\"HTTP Header Manager\" enabled=\"true\">\n            <collectionProp name=\"HeaderManager.headers\">\n              <elementProp name=\"\" elementType=\"Header\">\n                <stringProp name=\"Header.name\">User-Agent</stringProp>\n                <stringProp name=\"Header.value\">Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.103 Safari/537.36 </stringProp>\n              </elementProp>\n              <elementProp name=\"\" elementType=\"Header\">\n                <stringProp name=\"Header.name\">Accept</stringProp>\n                <stringProp name=\"Header.value\">*/*</stringProp>\n              </elementProp>\n              <elementProp name=\"\" elementType=\"Header\">\n                <stringProp name=\"Header.name\">Accept-Language</stringProp>\n                <stringProp name=\"Header.value\">en-us</stringProp>\n              </elementProp>\n            </collectionProp>\n          </HeaderManager>\n          <hashTree/>\n          <GenericController guiclass=\"LogicControllerGui\" testclass=\"GenericController\" testname=\"Moodle tests\" enabled=\"true\"/>\n          <hashTree>\n            <UserParameters guiclass=\"UserParametersGui\" testclass=\"UserParameters\" testname=\"User Parameters\" enabled=\"true\">\n              <boolProp name=\"TestElement.per_iteration\">true</boolProp>\n              <collectionProp name=\"UserParameters.names\">\n                <stringProp name=\"VIEWSTATE\">VIEWSTATE</stringProp>\n                <stringProp name=\"jsessionid\">jsessionid</stringProp>\n                <stringProp name=\"-532667892\">RandomNumber</stringProp>\n                <stringProp name=\"-265713450\">username</stringProp>\n                <stringProp name=\"-1797703761\">userRandomChoice</stringProp>\n                <stringProp name=\"1984989421\">sesskey</stringProp>\n                <stringProp name=\"636521343\">testBigFileLocation</stringProp>\n                <stringProp name=\"-1981424811\">testBigFileName</stringProp>\n                <stringProp name=\"775281475\">discussionid</stringProp>\n                <stringProp name=\"-1804370087\">courseList</stringProp>\n                <stringProp name=\"-2078389680\">xx_outputFromSplit</stringProp>\n              </collectionProp>\n              <collectionProp name=\"UserParameters.thread_values\">\n                <collectionProp name=\"2066100154\">\n                  <stringProp name=\"\"></stringProp>\n                  <stringProp name=\"\"></stringProp>\n                  <stringProp name=\"-2046088625\">${__Random(1,2000,ReuseRandomNumber)}</stringProp>\n                  <stringProp name=\"-1143784099\">m_azuretestuser_${__threadNum}</stringProp>\n                  <stringProp name=\"1345253498\">${RandomNumber}</stringProp>\n                  <stringProp name=\"-352988409\">${sesskey}</stringProp>\n                  <stringProp name=\"0\"></stringProp>\n                  <stringProp name=\"0\"></stringProp>\n                  <stringProp name=\"0\"></stringProp>\n                  <stringProp name=\"257720200\">${__V(course_list_${RandomNumber})}</stringProp>\n                  <stringProp name=\"-306911129\">${__split(${courseList}, course_id,|)}</stringProp>\n                </collectionProp>\n              </collectionProp>\n              <boolProp name=\"UserParameters.per_iteration\">true</boolProp>\n            </UserParameters>\n            <hashTree/>\n            <HTTPSamplerProxy guiclass=\"HttpTestSampleGui\" testclass=\"HTTPSamplerProxy\" testname=\"Goto Moodle\" enabled=\"true\">\n              <elementProp name=\"HTTPsampler.Arguments\" elementType=\"Arguments\" guiclass=\"HTTPArgumentsPanel\" testclass=\"Arguments\" testname=\"Argument List\" enabled=\"true\">\n                <collectionProp name=\"Arguments.arguments\"/>\n              </elementProp>\n              <stringProp name=\"HTTPSampler.domain\">${host}</stringProp>\n              <stringProp name=\"HTTPSampler.port\">${port}</stringProp>\n              <stringProp name=\"HTTPSampler.protocol\">${protocol}</stringProp>\n              <stringProp name=\"HTTPSampler.contentEncoding\"></stringProp>\n              <stringProp name=\"HTTPSampler.path\">/</stringProp>\n              <stringProp name=\"HTTPSampler.method\">GET</stringProp>\n              <boolProp name=\"HTTPSampler.follow_redirects\">true</boolProp>\n              <boolProp name=\"HTTPSampler.auto_redirects\">false</boolProp>\n              <boolProp name=\"HTTPSampler.use_keepalive\">true</boolProp>\n              <boolProp name=\"HTTPSampler.DO_MULTIPART_POST\">false</boolProp>\n              <stringProp name=\"HTTPSampler.embedded_url_re\"></stringProp>\n              <stringProp name=\"HTTPSampler.implementation\">Java</stringProp>\n              <stringProp name=\"HTTPSampler.connect_timeout\">${default_connect_timeout}</stringProp>\n              <stringProp name=\"HTTPSampler.response_timeout\">${default_response_timeout}</stringProp>\n            </HTTPSamplerProxy>\n            <hashTree>\n              <ConstantTimer guiclass=\"ConstantTimerGui\" testclass=\"ConstantTimer\" testname=\"Constant Timer\" enabled=\"false\">\n                <stringProp name=\"ConstantTimer.delay\">1000</stringProp>\n              </ConstantTimer>\n              <hashTree/>\n              <GaussianRandomTimer guiclass=\"GaussianRandomTimerGui\" testclass=\"GaussianRandomTimer\" testname=\"Gaussian Random Timer\" enabled=\"true\">\n                <stringProp name=\"ConstantTimer.delay\">8000</stringProp>\n                <stringProp name=\"RandomTimer.range\">4000</stringProp>\n              </GaussianRandomTimer>\n              <hashTree/>\n            </hashTree>\n            <HTTPSamplerProxy guiclass=\"HttpTestSampleGui\" testclass=\"HTTPSamplerProxy\" testname=\"Get Login\" enabled=\"true\">\n              <elementProp name=\"HTTPsampler.Arguments\" elementType=\"Arguments\" guiclass=\"HTTPArgumentsPanel\" testclass=\"Arguments\" testname=\"Argument List\" enabled=\"true\">\n                <collectionProp name=\"Arguments.arguments\"/>\n              </elementProp>\n              <stringProp name=\"HTTPSampler.domain\">${login_host}</stringProp>\n              <stringProp name=\"HTTPSampler.port\">${port}</stringProp>\n              <stringProp name=\"HTTPSampler.protocol\">${protocol}</stringProp>\n              <stringProp name=\"HTTPSampler.contentEncoding\"></stringProp>\n              <stringProp name=\"HTTPSampler.path\">/login/index.php</stringProp>\n              <stringProp name=\"HTTPSampler.method\">GET</stringProp>\n              <boolProp name=\"HTTPSampler.follow_redirects\">true</boolProp>\n              <boolProp name=\"HTTPSampler.auto_redirects\">false</boolProp>\n              <boolProp name=\"HTTPSampler.use_keepalive\">true</boolProp>\n              <boolProp name=\"HTTPSampler.DO_MULTIPART_POST\">false</boolProp>\n              <stringProp name=\"HTTPSampler.embedded_url_re\"></stringProp>\n              <stringProp name=\"HTTPSampler.implementation\">Java</stringProp>\n              <stringProp name=\"HTTPSampler.connect_timeout\">${default_connect_timeout}</stringProp>\n              <stringProp name=\"HTTPSampler.response_timeout\">${default_response_timeout}</stringProp>\n            </HTTPSamplerProxy>\n            <hashTree>\n              <ConstantTimer guiclass=\"ConstantTimerGui\" testclass=\"ConstantTimer\" testname=\"Constant Timer\" enabled=\"false\">\n                <stringProp name=\"ConstantTimer.delay\">1000</stringProp>\n              </ConstantTimer>\n              <hashTree/>\n              <GaussianRandomTimer guiclass=\"GaussianRandomTimerGui\" testclass=\"GaussianRandomTimer\" testname=\"Gaussian Random Timer\" enabled=\"false\">\n                <stringProp name=\"ConstantTimer.delay\">4000</stringProp>\n                <stringProp name=\"RandomTimer.range\">1000</stringProp>\n              </GaussianRandomTimer>\n              <hashTree/>\n              <HtmlExtractor guiclass=\"HtmlExtractorGui\" testclass=\"HtmlExtractor\" testname=\"CSS/JQuery Extractor\" enabled=\"true\">\n                <stringProp name=\"HtmlExtractor.refname\">logintoken</stringProp>\n                <stringProp name=\"HtmlExtractor.expr\">input[name=logintoken]</stringProp>\n                <stringProp name=\"HtmlExtractor.attribute\">value</stringProp>\n                <stringProp name=\"HtmlExtractor.default\">NULL</stringProp>\n                <boolProp name=\"HtmlExtractor.default_empty_value\">false</boolProp>\n                <stringProp name=\"HtmlExtractor.match_number\"></stringProp>\n                <stringProp name=\"HtmlExtractor.extractor_impl\"></stringProp>\n              </HtmlExtractor>\n              <hashTree/>\n            </hashTree>\n            <HTTPSamplerProxy guiclass=\"HttpTestSampleGui\" testclass=\"HTTPSamplerProxy\" testname=\"POST login\" enabled=\"true\">\n              <elementProp name=\"HTTPsampler.Arguments\" elementType=\"Arguments\" guiclass=\"HTTPArgumentsPanel\" testclass=\"Arguments\" testname=\"Argument List\" enabled=\"true\">\n                <collectionProp name=\"Arguments.arguments\">\n                  <elementProp name=\"username\" elementType=\"HTTPArgument\">\n                    <stringProp name=\"HTTPArgument.always_encode\">true</stringProp>\n                    <stringProp name=\"Argument.value\">${username}</stringProp>\n                    <stringProp name=\"Argument.name\">username</stringProp>\n                    <stringProp name=\"Argument.metadata\">=</stringProp>\n                    <stringProp name=\"Argument.use_equals\">true</stringProp>\n                    <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                  </elementProp>\n                  <elementProp name=\"password\" elementType=\"HTTPArgument\">\n                    <stringProp name=\"HTTPArgument.always_encode\">true</stringProp>\n                    <stringProp name=\"Argument.value\">${moodle_user_pass}</stringProp>\n                    <stringProp name=\"Argument.name\">password</stringProp>\n                    <stringProp name=\"Argument.metadata\">=</stringProp>\n                    <stringProp name=\"Argument.use_equals\">true</stringProp>\n                    <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                  </elementProp>\n                  <elementProp name=\"logintoken\" elementType=\"HTTPArgument\">\n                    <boolProp name=\"HTTPArgument.always_encode\">false</boolProp>\n                    <stringProp name=\"Argument.value\">${logintoken}</stringProp>\n                    <stringProp name=\"Argument.metadata\">=</stringProp>\n                    <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                    <stringProp name=\"Argument.name\">logintoken</stringProp>\n                  </elementProp>\n                </collectionProp>\n              </elementProp>\n              <stringProp name=\"HTTPSampler.domain\">${login_host}</stringProp>\n              <stringProp name=\"HTTPSampler.port\">${port}</stringProp>\n              <stringProp name=\"HTTPSampler.protocol\">${protocol}</stringProp>\n              <stringProp name=\"HTTPSampler.contentEncoding\"></stringProp>\n              <stringProp name=\"HTTPSampler.path\">/login/index.php</stringProp>\n              <stringProp name=\"HTTPSampler.method\">POST</stringProp>\n              <boolProp name=\"HTTPSampler.follow_redirects\">true</boolProp>\n              <boolProp name=\"HTTPSampler.auto_redirects\">false</boolProp>\n              <boolProp name=\"HTTPSampler.use_keepalive\">true</boolProp>\n              <boolProp name=\"HTTPSampler.DO_MULTIPART_POST\">false</boolProp>\n              <boolProp name=\"HTTPSampler.image_parser\">true</boolProp>\n              <stringProp name=\"HTTPSampler.embedded_url_re\"></stringProp>\n              <stringProp name=\"HTTPSampler.implementation\">Java</stringProp>\n              <stringProp name=\"HTTPSampler.connect_timeout\">${default_connect_timeout}</stringProp>\n              <stringProp name=\"HTTPSampler.response_timeout\">${default_response_timeout}</stringProp>\n            </HTTPSamplerProxy>\n            <hashTree>\n              <ResponseAssertion guiclass=\"AssertionGui\" testclass=\"ResponseAssertion\" testname=\"Assertion - Logged in Screen\" enabled=\"true\">\n                <collectionProp name=\"Asserion.test_strings\">\n                  <stringProp name=\"-1544167836\">You are logged in as </stringProp>\n                </collectionProp>\n                <intProp name=\"Assertion.test_type\">2</intProp>\n                <stringProp name=\"Assertion.test_field\">Assertion.response_data</stringProp>\n                <boolProp name=\"Assertion.assume_success\">false</boolProp>\n                <stringProp name=\"Assertion.custom_message\"></stringProp>\n              </ResponseAssertion>\n              <hashTree/>\n              <XPathExtractor guiclass=\"XPathExtractorGui\" testclass=\"XPathExtractor\" testname=\"Get sesskey\" enabled=\"true\">\n                <stringProp name=\"XPathExtractor.default\"></stringProp>\n                <stringProp name=\"XPathExtractor.refname\">sesskey</stringProp>\n                <stringProp name=\"XPathExtractor.xpathQuery\">//*[@name=&apos;sesskey&apos;][1]/@value</stringProp>\n                <boolProp name=\"XPathExtractor.validate\">false</boolProp>\n                <boolProp name=\"XPathExtractor.tolerant\">true</boolProp>\n                <boolProp name=\"XPathExtractor.namespace\">false</boolProp>\n                <stringProp name=\"XPathExtractor.matchNumber\">-1</stringProp>\n              </XPathExtractor>\n              <hashTree/>\n              <ConstantTimer guiclass=\"ConstantTimerGui\" testclass=\"ConstantTimer\" testname=\"Constant Timer\" enabled=\"false\">\n                <stringProp name=\"ConstantTimer.delay\">1000</stringProp>\n              </ConstantTimer>\n              <hashTree/>\n              <GaussianRandomTimer guiclass=\"GaussianRandomTimerGui\" testclass=\"GaussianRandomTimer\" testname=\"Gaussian Random Timer\" enabled=\"true\">\n                <stringProp name=\"ConstantTimer.delay\">5000</stringProp>\n                <stringProp name=\"RandomTimer.range\">0</stringProp>\n              </GaussianRandomTimer>\n              <hashTree/>\n              <ResultCollector guiclass=\"ViewResultsFullVisualizer\" testclass=\"ResultCollector\" testname=\"View Results Tree\" enabled=\"false\">\n                <boolProp name=\"ResultCollector.error_logging\">false</boolProp>\n                <objProp>\n                  <name>saveConfig</name>\n                  <value class=\"SampleSaveConfiguration\">\n                    <time>true</time>\n                    <latency>true</latency>\n                    <timestamp>true</timestamp>\n                    <success>true</success>\n                    <label>true</label>\n                    <code>true</code>\n                    <message>true</message>\n                    <threadName>true</threadName>\n                    <dataType>true</dataType>\n                    <encoding>false</encoding>\n                    <assertions>true</assertions>\n                    <subresults>true</subresults>\n                    <responseData>false</responseData>\n                    <samplerData>false</samplerData>\n                    <xml>false</xml>\n                    <fieldNames>true</fieldNames>\n                    <responseHeaders>false</responseHeaders>\n                    <requestHeaders>false</requestHeaders>\n                    <responseDataOnError>false</responseDataOnError>\n                    <saveAssertionResultsFailureMessage>true</saveAssertionResultsFailureMessage>\n                    <assertionsResultsToSave>0</assertionsResultsToSave>\n                    <bytes>true</bytes>\n                    <sentBytes>true</sentBytes>\n                    <threadCounts>true</threadCounts>\n                    <idleTime>true</idleTime>\n                    <connectTime>true</connectTime>\n                  </value>\n                </objProp>\n                <stringProp name=\"filename\"></stringProp>\n              </ResultCollector>\n              <hashTree/>\n            </hashTree>\n            <LoopController guiclass=\"LoopControlPanel\" testclass=\"LoopController\" testname=\"Loop\" enabled=\"true\">\n              <boolProp name=\"LoopController.continue_forever\">true</boolProp>\n              <stringProp name=\"LoopController.loops\">10</stringProp>\n            </LoopController>\n            <hashTree>\n              <GenericController guiclass=\"LogicControllerGui\" testclass=\"GenericController\" testname=\"Try Quiz\" enabled=\"true\"/>\n              <hashTree>\n                <HTTPSamplerProxy guiclass=\"HttpTestSampleGui\" testclass=\"HTTPSamplerProxy\" testname=\"Goto course\" enabled=\"true\">\n                  <elementProp name=\"HTTPsampler.Arguments\" elementType=\"Arguments\" guiclass=\"HTTPArgumentsPanel\" testclass=\"Arguments\" testname=\"Argument List\" enabled=\"true\">\n                    <collectionProp name=\"Arguments.arguments\">\n                      <elementProp name=\"id\" elementType=\"HTTPArgument\">\n                        <boolProp name=\"HTTPArgument.always_encode\">false</boolProp>\n                        <stringProp name=\"Argument.value\">${activity_course_id}</stringProp>\n                        <stringProp name=\"Argument.name\">id</stringProp>\n                        <stringProp name=\"Argument.metadata\">=</stringProp>\n                        <stringProp name=\"Argument.use_equals\">true</stringProp>\n                        <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                      </elementProp>\n                    </collectionProp>\n                  </elementProp>\n                  <stringProp name=\"HTTPSampler.domain\">${host}</stringProp>\n                  <stringProp name=\"HTTPSampler.port\">${port}</stringProp>\n                  <stringProp name=\"HTTPSampler.protocol\">${protocol}</stringProp>\n                  <stringProp name=\"HTTPSampler.contentEncoding\"></stringProp>\n                  <stringProp name=\"HTTPSampler.path\">/course/view.php</stringProp>\n                  <stringProp name=\"HTTPSampler.method\">GET</stringProp>\n                  <boolProp name=\"HTTPSampler.follow_redirects\">true</boolProp>\n                  <boolProp name=\"HTTPSampler.auto_redirects\">false</boolProp>\n                  <boolProp name=\"HTTPSampler.use_keepalive\">true</boolProp>\n                  <boolProp name=\"HTTPSampler.DO_MULTIPART_POST\">false</boolProp>\n                  <stringProp name=\"HTTPSampler.embedded_url_re\"></stringProp>\n                  <stringProp name=\"HTTPSampler.implementation\">Java</stringProp>\n                  <stringProp name=\"HTTPSampler.connect_timeout\"></stringProp>\n                  <stringProp name=\"HTTPSampler.response_timeout\"></stringProp>\n                </HTTPSamplerProxy>\n                <hashTree>\n                  <ResponseAssertion guiclass=\"AssertionGui\" testclass=\"ResponseAssertion\" testname=\"Assertion - in the course ?\" enabled=\"true\">\n                    <collectionProp name=\"Asserion.test_strings\">\n                      <stringProp name=\"-1205900583\">Performance Testing Regular Quiz</stringProp>\n                    </collectionProp>\n                    <intProp name=\"Assertion.test_type\">2</intProp>\n                    <stringProp name=\"Assertion.test_field\">Assertion.response_data</stringProp>\n                    <boolProp name=\"Assertion.assume_success\">true</boolProp>\n                    <stringProp name=\"Assertion.custom_message\"></stringProp>\n                  </ResponseAssertion>\n                  <hashTree/>\n                  <RegexExtractor guiclass=\"RegexExtractorGui\" testclass=\"RegexExtractor\" testname=\"RegExp Extractor - Get forum discussion headings (if any!)\" enabled=\"false\">\n                    <stringProp name=\"RegexExtractor.useHeaders\">false</stringProp>\n                    <stringProp name=\"RegexExtractor.refname\">classtopics_html</stringProp>\n                    <stringProp name=\"RegexExtractor.regex\">(?s)(&lt;ul class=&quot;topics&quot;.*?)&lt;\\/div&gt;.*?&lt;\\/div&gt;.*?&lt;\\/div&gt;.*?&lt;\\/div&gt;.*?&lt;\\/div&gt;.*?&lt;div id=&quot;region-pre&quot;</stringProp>\n                    <stringProp name=\"RegexExtractor.template\">$1$</stringProp>\n                    <stringProp name=\"RegexExtractor.default\">NULL</stringProp>\n                    <stringProp name=\"RegexExtractor.match_number\"></stringProp>\n                  </RegexExtractor>\n                  <hashTree/>\n                  <RegexExtractor guiclass=\"RegexExtractorGui\" testclass=\"RegexExtractor\" testname=\"RegExp Extractor - Get perf quiz chunk\" enabled=\"true\">\n                    <stringProp name=\"RegexExtractor.useHeaders\">false</stringProp>\n                    <stringProp name=\"RegexExtractor.refname\">html_chunk_perfquiz</stringProp>\n                    <stringProp name=\"RegexExtractor.regex\">(.{0,250}Performance Testing Regular Quiz)</stringProp>\n                    <stringProp name=\"RegexExtractor.template\">$1$</stringProp>\n                    <stringProp name=\"RegexExtractor.default\">NULL</stringProp>\n                    <stringProp name=\"RegexExtractor.match_number\"></stringProp>\n                  </RegexExtractor>\n                  <hashTree/>\n                  <RegexExtractor guiclass=\"RegexExtractorGui\" testclass=\"RegexExtractor\" testname=\"RegExp Extractor - Get forum discussion headings (if any!)\" enabled=\"true\">\n                    <stringProp name=\"RegexExtractor.useHeaders\">false</stringProp>\n                    <stringProp name=\"RegexExtractor.refname\">moduleid</stringProp>\n                    <stringProp name=\"RegexExtractor.regex\">\\/mod\\/quiz\\/view\\.php\\?id=(\\d+)</stringProp>\n                    <stringProp name=\"RegexExtractor.template\">$1$</stringProp>\n                    <stringProp name=\"RegexExtractor.default\">NULL</stringProp>\n                    <stringProp name=\"RegexExtractor.match_number\"></stringProp>\n                    <stringProp name=\"Sample.scope\">variable</stringProp>\n                    <stringProp name=\"Scope.variable\">html_chunk_perfquiz</stringProp>\n                  </RegexExtractor>\n                  <hashTree/>\n                  <XPathExtractor guiclass=\"XPathExtractorGui\" testclass=\"XPathExtractor\" testname=\"XPath Extractor - Get Performance Test Regular Quiz id\" enabled=\"false\">\n                    <stringProp name=\"XPathExtractor.default\">NULL</stringProp>\n                    <stringProp name=\"XPathExtractor.refname\">moduleid</stringProp>\n                    <stringProp name=\"XPathExtractor.xpathQuery\">substring-after(//*[contains(text(),&apos;Performance Testing Regular Quiz&apos;)]/../@href,&apos;id=&apos;)</stringProp>\n                    <boolProp name=\"XPathExtractor.validate\">false</boolProp>\n                    <boolProp name=\"XPathExtractor.tolerant\">true</boolProp>\n                    <boolProp name=\"XPathExtractor.namespace\">false</boolProp>\n                    <boolProp name=\"XPathExtractor.show_warnings\">true</boolProp>\n                    <boolProp name=\"XPathExtractor.quiet\">false</boolProp>\n                    <boolProp name=\"XPathExtractor.report_errors\">true</boolProp>\n                    <stringProp name=\"Sample.scope\">variable</stringProp>\n                    <stringProp name=\"Scope.variable\">classtopics_html</stringProp>\n                    <stringProp name=\"XPathExtractor.matchNumber\">-1</stringProp>\n                  </XPathExtractor>\n                  <hashTree/>\n                  <XPathExtractor guiclass=\"XPathExtractorGui\" testclass=\"XPathExtractor\" testname=\"Get module id\" enabled=\"false\">\n                    <stringProp name=\"XPathExtractor.default\"></stringProp>\n                    <stringProp name=\"XPathExtractor.refname\">moduleid</stringProp>\n                    <stringProp name=\"XPathExtractor.xpathQuery\">substring-after(//*/a/*[contains(text(),&apos;Performance Testing Regular Quiz&apos;)]/../@href,&apos;=&apos;)</stringProp>\n                    <boolProp name=\"XPathExtractor.validate\">false</boolProp>\n                    <boolProp name=\"XPathExtractor.tolerant\">true</boolProp>\n                    <boolProp name=\"XPathExtractor.namespace\">false</boolProp>\n                    <boolProp name=\"XPathExtractor.show_warnings\">true</boolProp>\n                    <boolProp name=\"XPathExtractor.report_errors\">true</boolProp>\n                    <boolProp name=\"XPathExtractor.quiet\">false</boolProp>\n                    <stringProp name=\"XPathExtractor.matchNumber\">-1</stringProp>\n                  </XPathExtractor>\n                  <hashTree/>\n                  <GaussianRandomTimer guiclass=\"GaussianRandomTimerGui\" testclass=\"GaussianRandomTimer\" testname=\"Gaussian Random Timer\" enabled=\"true\">\n                    <stringProp name=\"ConstantTimer.delay\">2000</stringProp>\n                    <stringProp name=\"RandomTimer.range\">1000</stringProp>\n                  </GaussianRandomTimer>\n                  <hashTree/>\n                  <ConstantTimer guiclass=\"ConstantTimerGui\" testclass=\"ConstantTimer\" testname=\"Constant Timer\" enabled=\"false\">\n                    <stringProp name=\"ConstantTimer.delay\">1000</stringProp>\n                  </ConstantTimer>\n                  <hashTree/>\n                  <ResultCollector guiclass=\"ViewResultsFullVisualizer\" testclass=\"ResultCollector\" testname=\"View Results Tree\" enabled=\"false\">\n                    <boolProp name=\"ResultCollector.error_logging\">false</boolProp>\n                    <objProp>\n                      <name>saveConfig</name>\n                      <value class=\"SampleSaveConfiguration\">\n                        <time>true</time>\n                        <latency>true</latency>\n                        <timestamp>true</timestamp>\n                        <success>true</success>\n                        <label>true</label>\n                        <code>true</code>\n                        <message>true</message>\n                        <threadName>true</threadName>\n                        <dataType>true</dataType>\n                        <encoding>false</encoding>\n                        <assertions>true</assertions>\n                        <subresults>true</subresults>\n                        <responseData>false</responseData>\n                        <samplerData>false</samplerData>\n                        <xml>false</xml>\n                        <fieldNames>true</fieldNames>\n                        <responseHeaders>false</responseHeaders>\n                        <requestHeaders>false</requestHeaders>\n                        <responseDataOnError>false</responseDataOnError>\n                        <saveAssertionResultsFailureMessage>true</saveAssertionResultsFailureMessage>\n                        <assertionsResultsToSave>0</assertionsResultsToSave>\n                        <bytes>true</bytes>\n                        <sentBytes>true</sentBytes>\n                        <threadCounts>true</threadCounts>\n                        <idleTime>true</idleTime>\n                        <connectTime>true</connectTime>\n                      </value>\n                    </objProp>\n                    <stringProp name=\"filename\"></stringProp>\n                  </ResultCollector>\n                  <hashTree/>\n                </hashTree>\n                <WhileController guiclass=\"WhileControllerGui\" testclass=\"WhileController\" testname=\"Poll while quiz hasn&apos;t started\" enabled=\"true\">\n                  <stringProp name=\"WhileController.condition\">${__javaScript(&quot;${quiz_opened}&quot; != &quot;This quiz opened at&quot;)}</stringProp>\n                </WhileController>\n                <hashTree>\n                  <HTTPSamplerProxy guiclass=\"HttpTestSampleGui\" testclass=\"HTTPSamplerProxy\" testname=\"Open the quiz\" enabled=\"true\">\n                    <elementProp name=\"HTTPsampler.Arguments\" elementType=\"Arguments\" guiclass=\"HTTPArgumentsPanel\" testclass=\"Arguments\" testname=\"Argument List\" enabled=\"true\">\n                      <collectionProp name=\"Arguments.arguments\">\n                        <elementProp name=\"id\" elementType=\"HTTPArgument\">\n                          <boolProp name=\"HTTPArgument.always_encode\">false</boolProp>\n                          <stringProp name=\"Argument.value\">${moduleid}</stringProp>\n                          <stringProp name=\"Argument.name\">id</stringProp>\n                          <stringProp name=\"Argument.metadata\">=</stringProp>\n                          <stringProp name=\"Argument.use_equals\">true</stringProp>\n                          <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                        </elementProp>\n                      </collectionProp>\n                    </elementProp>\n                    <stringProp name=\"HTTPSampler.domain\">${host}</stringProp>\n                    <stringProp name=\"HTTPSampler.port\">${port}</stringProp>\n                    <stringProp name=\"HTTPSampler.protocol\">${protocol}</stringProp>\n                    <stringProp name=\"HTTPSampler.contentEncoding\"></stringProp>\n                    <stringProp name=\"HTTPSampler.path\">/mod/quiz/view.php</stringProp>\n                    <stringProp name=\"HTTPSampler.method\">GET</stringProp>\n                    <boolProp name=\"HTTPSampler.follow_redirects\">true</boolProp>\n                    <boolProp name=\"HTTPSampler.auto_redirects\">false</boolProp>\n                    <boolProp name=\"HTTPSampler.use_keepalive\">true</boolProp>\n                    <boolProp name=\"HTTPSampler.DO_MULTIPART_POST\">false</boolProp>\n                    <stringProp name=\"HTTPSampler.embedded_url_re\"></stringProp>\n                    <stringProp name=\"HTTPSampler.implementation\">Java</stringProp>\n                    <stringProp name=\"HTTPSampler.connect_timeout\"></stringProp>\n                    <stringProp name=\"HTTPSampler.response_timeout\"></stringProp>\n                  </HTTPSamplerProxy>\n                  <hashTree>\n                    <RegexExtractor guiclass=\"RegexExtractorGui\" testclass=\"RegexExtractor\" testname=\"Regular Expression Extractor - Check if quiz is open now\" enabled=\"true\">\n                      <stringProp name=\"RegexExtractor.useHeaders\">false</stringProp>\n                      <stringProp name=\"RegexExtractor.refname\">quiz_opened</stringProp>\n                      <stringProp name=\"RegexExtractor.regex\">(This quiz opened at)</stringProp>\n                      <stringProp name=\"RegexExtractor.template\">$1$</stringProp>\n                      <stringProp name=\"RegexExtractor.default\"></stringProp>\n                      <stringProp name=\"RegexExtractor.match_number\"></stringProp>\n                      <boolProp name=\"RegexExtractor.default_empty_value\">true</boolProp>\n                    </RegexExtractor>\n                    <hashTree/>\n                    <ResponseAssertion guiclass=\"AssertionGui\" testclass=\"ResponseAssertion\" testname=\"Assertion - quiz hasn&apos;t started?\" enabled=\"false\">\n                      <collectionProp name=\"Asserion.test_strings\">\n                        <stringProp name=\"956891785\">The quiz will not be available until</stringProp>\n                      </collectionProp>\n                      <stringProp name=\"Assertion.test_field\">Assertion.response_data</stringProp>\n                      <boolProp name=\"Assertion.assume_success\">false</boolProp>\n                      <intProp name=\"Assertion.test_type\">2</intProp>\n                      <stringProp name=\"Assertion.custom_message\"></stringProp>\n                    </ResponseAssertion>\n                    <hashTree/>\n                    <XPathExtractor guiclass=\"XPathExtractorGui\" testclass=\"XPathExtractor\" testname=\"Get quiz id\" enabled=\"false\">\n                      <stringProp name=\"XPathExtractor.default\"></stringProp>\n                      <stringProp name=\"XPathExtractor.refname\">quizid</stringProp>\n                      <stringProp name=\"XPathExtractor.xpathQuery\">//*/form[@action=&quot;https://${host}/mod/quiz/startattempt.php&quot;]//*/input[@name=&apos;cmid&apos;][1]/@value</stringProp>\n                      <boolProp name=\"XPathExtractor.validate\">false</boolProp>\n                      <boolProp name=\"XPathExtractor.tolerant\">true</boolProp>\n                      <boolProp name=\"XPathExtractor.namespace\">false</boolProp>\n                      <boolProp name=\"XPathExtractor.show_warnings\">true</boolProp>\n                      <boolProp name=\"XPathExtractor.report_errors\">true</boolProp>\n                      <boolProp name=\"XPathExtractor.quiet\">false</boolProp>\n                      <stringProp name=\"XPathExtractor.matchNumber\">-1</stringProp>\n                    </XPathExtractor>\n                    <hashTree/>\n                    <GaussianRandomTimer guiclass=\"GaussianRandomTimerGui\" testclass=\"GaussianRandomTimer\" testname=\"Gaussian Random Timer\" enabled=\"true\">\n                      <stringProp name=\"ConstantTimer.delay\">500</stringProp>\n                      <stringProp name=\"RandomTimer.range\">200</stringProp>\n                    </GaussianRandomTimer>\n                    <hashTree/>\n                    <ConstantTimer guiclass=\"ConstantTimerGui\" testclass=\"ConstantTimer\" testname=\"Constant Timer\" enabled=\"false\">\n                      <stringProp name=\"ConstantTimer.delay\">1000</stringProp>\n                    </ConstantTimer>\n                    <hashTree/>\n                    <ResultCollector guiclass=\"ViewResultsFullVisualizer\" testclass=\"ResultCollector\" testname=\"View Results Tree\" enabled=\"false\">\n                      <boolProp name=\"ResultCollector.error_logging\">false</boolProp>\n                      <objProp>\n                        <name>saveConfig</name>\n                        <value class=\"SampleSaveConfiguration\">\n                          <time>true</time>\n                          <latency>true</latency>\n                          <timestamp>true</timestamp>\n                          <success>true</success>\n                          <label>true</label>\n                          <code>true</code>\n                          <message>true</message>\n                          <threadName>true</threadName>\n                          <dataType>true</dataType>\n                          <encoding>false</encoding>\n                          <assertions>true</assertions>\n                          <subresults>true</subresults>\n                          <responseData>false</responseData>\n                          <samplerData>false</samplerData>\n                          <xml>false</xml>\n                          <fieldNames>true</fieldNames>\n                          <responseHeaders>false</responseHeaders>\n                          <requestHeaders>false</requestHeaders>\n                          <responseDataOnError>false</responseDataOnError>\n                          <saveAssertionResultsFailureMessage>true</saveAssertionResultsFailureMessage>\n                          <assertionsResultsToSave>0</assertionsResultsToSave>\n                          <bytes>true</bytes>\n                          <sentBytes>true</sentBytes>\n                          <threadCounts>true</threadCounts>\n                          <idleTime>true</idleTime>\n                          <connectTime>true</connectTime>\n                        </value>\n                      </objProp>\n                      <stringProp name=\"filename\"></stringProp>\n                    </ResultCollector>\n                    <hashTree/>\n                  </hashTree>\n                </hashTree>\n                <HTTPSamplerProxy guiclass=\"HttpTestSampleGui\" testclass=\"HTTPSamplerProxy\" testname=\"Click on Attempt the quiz now\" enabled=\"true\">\n                  <elementProp name=\"HTTPsampler.Arguments\" elementType=\"Arguments\" guiclass=\"HTTPArgumentsPanel\" testclass=\"Arguments\" testname=\"Argument List\" enabled=\"true\">\n                    <collectionProp name=\"Arguments.arguments\">\n                      <elementProp name=\"cmid\" elementType=\"HTTPArgument\">\n                        <boolProp name=\"HTTPArgument.always_encode\">false</boolProp>\n                        <stringProp name=\"Argument.value\">${moduleid}</stringProp>\n                        <stringProp name=\"Argument.name\">cmid</stringProp>\n                        <stringProp name=\"Argument.metadata\">=</stringProp>\n                        <stringProp name=\"Argument.use_equals\">true</stringProp>\n                        <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                      </elementProp>\n                      <elementProp name=\"sesskey\" elementType=\"HTTPArgument\">\n                        <boolProp name=\"HTTPArgument.always_encode\">false</boolProp>\n                        <stringProp name=\"Argument.value\">${sesskey}</stringProp>\n                        <stringProp name=\"Argument.metadata\">=</stringProp>\n                        <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                        <stringProp name=\"Argument.name\">sesskey</stringProp>\n                      </elementProp>\n                    </collectionProp>\n                  </elementProp>\n                  <stringProp name=\"HTTPSampler.domain\">${host}</stringProp>\n                  <stringProp name=\"HTTPSampler.port\">${port}</stringProp>\n                  <stringProp name=\"HTTPSampler.protocol\">${protocol}</stringProp>\n                  <stringProp name=\"HTTPSampler.contentEncoding\"></stringProp>\n                  <stringProp name=\"HTTPSampler.path\">/mod/quiz/startattempt.php</stringProp>\n                  <stringProp name=\"HTTPSampler.method\">POST</stringProp>\n                  <boolProp name=\"HTTPSampler.follow_redirects\">true</boolProp>\n                  <boolProp name=\"HTTPSampler.auto_redirects\">false</boolProp>\n                  <boolProp name=\"HTTPSampler.use_keepalive\">true</boolProp>\n                  <boolProp name=\"HTTPSampler.DO_MULTIPART_POST\">false</boolProp>\n                  <stringProp name=\"HTTPSampler.embedded_url_re\"></stringProp>\n                  <stringProp name=\"HTTPSampler.implementation\">Java</stringProp>\n                  <stringProp name=\"HTTPSampler.connect_timeout\"></stringProp>\n                  <stringProp name=\"HTTPSampler.response_timeout\"></stringProp>\n                </HTTPSamplerProxy>\n                <hashTree>\n                  <ResponseAssertion guiclass=\"AssertionGui\" testclass=\"ResponseAssertion\" testname=\"Assertion - in the quiz ?\" enabled=\"true\">\n                    <collectionProp name=\"Asserion.test_strings\">\n                      <stringProp name=\"-1545612013\">What&apos;s your username</stringProp>\n                      <stringProp name=\"-2127233155\">Is the LMS Performing acceptably?</stringProp>\n                      <stringProp name=\"-1512513172\">What is 2 \\+ 2\\?</stringProp>\n                    </collectionProp>\n                    <stringProp name=\"Assertion.test_field\">Assertion.response_data</stringProp>\n                    <boolProp name=\"Assertion.assume_success\">false</boolProp>\n                    <intProp name=\"Assertion.test_type\">2</intProp>\n                    <stringProp name=\"Assertion.custom_message\"></stringProp>\n                  </ResponseAssertion>\n                  <hashTree/>\n                  <GaussianRandomTimer guiclass=\"GaussianRandomTimerGui\" testclass=\"GaussianRandomTimer\" testname=\"Gaussian Random Timer\" enabled=\"true\">\n                    <stringProp name=\"ConstantTimer.delay\">2000</stringProp>\n                    <stringProp name=\"RandomTimer.range\">1000</stringProp>\n                  </GaussianRandomTimer>\n                  <hashTree/>\n                  <ConstantTimer guiclass=\"ConstantTimerGui\" testclass=\"ConstantTimer\" testname=\"Constant Timer\" enabled=\"false\">\n                    <stringProp name=\"ConstantTimer.delay\">1000</stringProp>\n                  </ConstantTimer>\n                  <hashTree/>\n                  <XPathExtractor guiclass=\"XPathExtractorGui\" testclass=\"XPathExtractor\" testname=\"Get attemptid\" enabled=\"false\">\n                    <stringProp name=\"XPathExtractor.default\"></stringProp>\n                    <stringProp name=\"XPathExtractor.refname\">attemptid</stringProp>\n                    <stringProp name=\"XPathExtractor.xpathQuery\">//*/form[@action=&quot;https://${host}/mod/quiz/processattempt.php&quot;]//*/input[@name=&apos;attempt&apos;][1]/@value</stringProp>\n                    <boolProp name=\"XPathExtractor.validate\">false</boolProp>\n                    <boolProp name=\"XPathExtractor.tolerant\">true</boolProp>\n                    <boolProp name=\"XPathExtractor.namespace\">false</boolProp>\n                    <boolProp name=\"XPathExtractor.show_warnings\">true</boolProp>\n                    <boolProp name=\"XPathExtractor.report_errors\">true</boolProp>\n                    <boolProp name=\"XPathExtractor.quiet\">false</boolProp>\n                    <stringProp name=\"XPathExtractor.matchNumber\">-1</stringProp>\n                  </XPathExtractor>\n                  <hashTree/>\n                  <RegexExtractor guiclass=\"RegexExtractorGui\" testclass=\"RegexExtractor\" testname=\"RegExp - Get attemptip\" enabled=\"true\">\n                    <stringProp name=\"RegexExtractor.useHeaders\">false</stringProp>\n                    <stringProp name=\"RegexExtractor.refname\">attemptid</stringProp>\n                    <stringProp name=\"RegexExtractor.regex\">\\/mod\\/quiz\\/attempt\\.php\\?attempt=(\\d+).*?&quot;&gt;Continue&lt;\\/a&gt;</stringProp>\n                    <stringProp name=\"RegexExtractor.template\">$1$</stringProp>\n                    <stringProp name=\"RegexExtractor.default\">NULL</stringProp>\n                    <stringProp name=\"RegexExtractor.match_number\"></stringProp>\n                    <stringProp name=\"Sample.scope\">children</stringProp>\n                  </RegexExtractor>\n                  <hashTree/>\n                  <ResultCollector guiclass=\"ViewResultsFullVisualizer\" testclass=\"ResultCollector\" testname=\"View Results Tree\" enabled=\"false\">\n                    <boolProp name=\"ResultCollector.error_logging\">false</boolProp>\n                    <objProp>\n                      <name>saveConfig</name>\n                      <value class=\"SampleSaveConfiguration\">\n                        <time>true</time>\n                        <latency>true</latency>\n                        <timestamp>true</timestamp>\n                        <success>true</success>\n                        <label>true</label>\n                        <code>true</code>\n                        <message>true</message>\n                        <threadName>true</threadName>\n                        <dataType>true</dataType>\n                        <encoding>false</encoding>\n                        <assertions>true</assertions>\n                        <subresults>true</subresults>\n                        <responseData>false</responseData>\n                        <samplerData>false</samplerData>\n                        <xml>false</xml>\n                        <fieldNames>true</fieldNames>\n                        <responseHeaders>false</responseHeaders>\n                        <requestHeaders>false</requestHeaders>\n                        <responseDataOnError>false</responseDataOnError>\n                        <saveAssertionResultsFailureMessage>true</saveAssertionResultsFailureMessage>\n                        <assertionsResultsToSave>0</assertionsResultsToSave>\n                        <bytes>true</bytes>\n                        <sentBytes>true</sentBytes>\n                        <threadCounts>true</threadCounts>\n                        <idleTime>true</idleTime>\n                        <connectTime>true</connectTime>\n                      </value>\n                    </objProp>\n                    <stringProp name=\"filename\"></stringProp>\n                  </ResultCollector>\n                  <hashTree/>\n                </hashTree>\n                <HTTPSamplerProxy guiclass=\"HttpTestSampleGui\" testclass=\"HTTPSamplerProxy\" testname=\"Submit the quiz attempt\" enabled=\"true\">\n                  <elementProp name=\"HTTPsampler.Arguments\" elementType=\"Arguments\" guiclass=\"HTTPArgumentsPanel\" testclass=\"Arguments\" testname=\"Argument List\" enabled=\"true\">\n                    <collectionProp name=\"Arguments.arguments\">\n                      <elementProp name=\"q${attemptid}:1_:flagged\" elementType=\"HTTPArgument\">\n                        <boolProp name=\"HTTPArgument.always_encode\">false</boolProp>\n                        <stringProp name=\"Argument.value\">0</stringProp>\n                        <stringProp name=\"Argument.metadata\">=</stringProp>\n                        <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                        <stringProp name=\"Argument.name\">q${attemptid}:1_:flagged</stringProp>\n                      </elementProp>\n                      <elementProp name=\"q${attemptid}:1_:sequencecheck\" elementType=\"HTTPArgument\">\n                        <boolProp name=\"HTTPArgument.always_encode\">false</boolProp>\n                        <stringProp name=\"Argument.value\">1</stringProp>\n                        <stringProp name=\"Argument.metadata\">=</stringProp>\n                        <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                        <stringProp name=\"Argument.name\">q${attemptid}:1_:sequencecheck</stringProp>\n                      </elementProp>\n                      <elementProp name=\"q${attemptid}:1_answer\" elementType=\"HTTPArgument\">\n                        <boolProp name=\"HTTPArgument.always_encode\">false</boolProp>\n                        <stringProp name=\"Argument.value\">${username}@${user_type}</stringProp>\n                        <stringProp name=\"Argument.metadata\">=</stringProp>\n                        <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                        <stringProp name=\"Argument.name\">q${attemptid}:1_answer</stringProp>\n                      </elementProp>\n                      <elementProp name=\"q${attemptid}:2_:flagged\" elementType=\"HTTPArgument\">\n                        <boolProp name=\"HTTPArgument.always_encode\">false</boolProp>\n                        <stringProp name=\"Argument.value\">0</stringProp>\n                        <stringProp name=\"Argument.metadata\">=</stringProp>\n                        <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                        <stringProp name=\"Argument.name\">q${attemptid}:2_:flagged</stringProp>\n                      </elementProp>\n                      <elementProp name=\"q${attemptid}:2_:sequencecheck\" elementType=\"HTTPArgument\">\n                        <boolProp name=\"HTTPArgument.always_encode\">false</boolProp>\n                        <stringProp name=\"Argument.value\">1</stringProp>\n                        <stringProp name=\"Argument.metadata\">=</stringProp>\n                        <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                        <stringProp name=\"Argument.name\">q${attemptid}:2_:sequencecheck</stringProp>\n                      </elementProp>\n                      <elementProp name=\"q${attemptid}:2_answer\" elementType=\"HTTPArgument\">\n                        <boolProp name=\"HTTPArgument.always_encode\">false</boolProp>\n                        <stringProp name=\"Argument.value\">${__Random(0,1)}</stringProp>\n                        <stringProp name=\"Argument.metadata\">=</stringProp>\n                        <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                        <stringProp name=\"Argument.name\">q${attemptid}:2_answer</stringProp>\n                      </elementProp>\n                      <elementProp name=\"q${attemptid}:3_:flagged\" elementType=\"HTTPArgument\">\n                        <boolProp name=\"HTTPArgument.always_encode\">false</boolProp>\n                        <stringProp name=\"Argument.value\">0</stringProp>\n                        <stringProp name=\"Argument.metadata\">=</stringProp>\n                        <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                        <stringProp name=\"Argument.name\">q${attemptid}:3_:flagged</stringProp>\n                      </elementProp>\n                      <elementProp name=\"q${attemptid}:3_:sequencecheck\" elementType=\"HTTPArgument\">\n                        <boolProp name=\"HTTPArgument.always_encode\">false</boolProp>\n                        <stringProp name=\"Argument.value\">1</stringProp>\n                        <stringProp name=\"Argument.metadata\">=</stringProp>\n                        <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                        <stringProp name=\"Argument.name\">q${attemptid}:3_:sequencecheck</stringProp>\n                      </elementProp>\n                      <elementProp name=\"q${attemptid}:3_answer\" elementType=\"HTTPArgument\">\n                        <boolProp name=\"HTTPArgument.always_encode\">false</boolProp>\n                        <stringProp name=\"Argument.value\">${__Random(3,5)}</stringProp>\n                        <stringProp name=\"Argument.metadata\">=</stringProp>\n                        <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                        <stringProp name=\"Argument.name\">q${attemptid}:3_answer</stringProp>\n                      </elementProp>\n                      <elementProp name=\"next\" elementType=\"HTTPArgument\">\n                        <boolProp name=\"HTTPArgument.always_encode\">false</boolProp>\n                        <stringProp name=\"Argument.value\">Next</stringProp>\n                        <stringProp name=\"Argument.metadata\">=</stringProp>\n                        <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                        <stringProp name=\"Argument.name\">next</stringProp>\n                      </elementProp>\n                      <elementProp name=\"attempt\" elementType=\"HTTPArgument\">\n                        <boolProp name=\"HTTPArgument.always_encode\">false</boolProp>\n                        <stringProp name=\"Argument.value\">${attemptid}</stringProp>\n                        <stringProp name=\"Argument.metadata\">=</stringProp>\n                        <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                        <stringProp name=\"Argument.name\">attempt</stringProp>\n                      </elementProp>\n                      <elementProp name=\"thispage\" elementType=\"HTTPArgument\">\n                        <boolProp name=\"HTTPArgument.always_encode\">false</boolProp>\n                        <stringProp name=\"Argument.value\">0</stringProp>\n                        <stringProp name=\"Argument.metadata\">=</stringProp>\n                        <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                        <stringProp name=\"Argument.name\">thispage</stringProp>\n                      </elementProp>\n                      <elementProp name=\"nextpage\" elementType=\"HTTPArgument\">\n                        <boolProp name=\"HTTPArgument.always_encode\">false</boolProp>\n                        <stringProp name=\"Argument.value\">-1</stringProp>\n                        <stringProp name=\"Argument.metadata\">=</stringProp>\n                        <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                        <stringProp name=\"Argument.name\">nextpage</stringProp>\n                      </elementProp>\n                      <elementProp name=\"timeup\" elementType=\"HTTPArgument\">\n                        <boolProp name=\"HTTPArgument.always_encode\">false</boolProp>\n                        <stringProp name=\"Argument.value\">0</stringProp>\n                        <stringProp name=\"Argument.metadata\">=</stringProp>\n                        <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                        <stringProp name=\"Argument.name\">timeup</stringProp>\n                      </elementProp>\n                      <elementProp name=\"sesskey\" elementType=\"HTTPArgument\">\n                        <boolProp name=\"HTTPArgument.always_encode\">false</boolProp>\n                        <stringProp name=\"Argument.value\">${sesskey}</stringProp>\n                        <stringProp name=\"Argument.metadata\">=</stringProp>\n                        <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                        <stringProp name=\"Argument.name\">sesskey</stringProp>\n                      </elementProp>\n                      <elementProp name=\"scrollpos\" elementType=\"HTTPArgument\">\n                        <boolProp name=\"HTTPArgument.always_encode\">false</boolProp>\n                        <stringProp name=\"Argument.value\"></stringProp>\n                        <stringProp name=\"Argument.metadata\">=</stringProp>\n                        <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                        <stringProp name=\"Argument.name\">scrollpos</stringProp>\n                      </elementProp>\n                      <elementProp name=\"slots\" elementType=\"HTTPArgument\">\n                        <boolProp name=\"HTTPArgument.always_encode\">false</boolProp>\n                        <stringProp name=\"Argument.value\">1,2,3</stringProp>\n                        <stringProp name=\"Argument.metadata\">=</stringProp>\n                        <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                        <stringProp name=\"Argument.name\">slots</stringProp>\n                      </elementProp>\n                    </collectionProp>\n                  </elementProp>\n                  <stringProp name=\"HTTPSampler.domain\">${host}</stringProp>\n                  <stringProp name=\"HTTPSampler.port\">${port}</stringProp>\n                  <stringProp name=\"HTTPSampler.protocol\">${protocol}</stringProp>\n                  <stringProp name=\"HTTPSampler.contentEncoding\">utf-8</stringProp>\n                  <stringProp name=\"HTTPSampler.path\">/mod/quiz/processattempt.php</stringProp>\n                  <stringProp name=\"HTTPSampler.method\">POST</stringProp>\n                  <boolProp name=\"HTTPSampler.follow_redirects\">true</boolProp>\n                  <boolProp name=\"HTTPSampler.auto_redirects\">false</boolProp>\n                  <boolProp name=\"HTTPSampler.use_keepalive\">true</boolProp>\n                  <boolProp name=\"HTTPSampler.DO_MULTIPART_POST\">true</boolProp>\n                  <stringProp name=\"HTTPSampler.embedded_url_re\"></stringProp>\n                  <stringProp name=\"HTTPSampler.implementation\">Java</stringProp>\n                  <stringProp name=\"HTTPSampler.connect_timeout\"></stringProp>\n                  <stringProp name=\"HTTPSampler.response_timeout\"></stringProp>\n                </HTTPSamplerProxy>\n                <hashTree>\n                  <ResponseAssertion guiclass=\"AssertionGui\" testclass=\"ResponseAssertion\" testname=\"Assertion - Response Contains Summary of attempt\" enabled=\"true\">\n                    <collectionProp name=\"Asserion.test_strings\">\n                      <stringProp name=\"-999752098\">Summary of attempt</stringProp>\n                    </collectionProp>\n                    <intProp name=\"Assertion.test_type\">2</intProp>\n                    <stringProp name=\"Assertion.test_field\">Assertion.response_data</stringProp>\n                    <boolProp name=\"Assertion.assume_success\">false</boolProp>\n                    <stringProp name=\"Assertion.custom_message\"></stringProp>\n                  </ResponseAssertion>\n                  <hashTree/>\n                  <GaussianRandomTimer guiclass=\"GaussianRandomTimerGui\" testclass=\"GaussianRandomTimer\" testname=\"Gaussian Random Timer\" enabled=\"true\">\n                    <stringProp name=\"ConstantTimer.delay\">2000</stringProp>\n                    <stringProp name=\"RandomTimer.range\">1000</stringProp>\n                  </GaussianRandomTimer>\n                  <hashTree/>\n                  <ConstantTimer guiclass=\"ConstantTimerGui\" testclass=\"ConstantTimer\" testname=\"Constant Timer\" enabled=\"false\">\n                    <stringProp name=\"ConstantTimer.delay\">1000</stringProp>\n                  </ConstantTimer>\n                  <hashTree/>\n                  <ResultCollector guiclass=\"ViewResultsFullVisualizer\" testclass=\"ResultCollector\" testname=\"View Results Tree\" enabled=\"false\">\n                    <boolProp name=\"ResultCollector.error_logging\">false</boolProp>\n                    <objProp>\n                      <name>saveConfig</name>\n                      <value class=\"SampleSaveConfiguration\">\n                        <time>true</time>\n                        <latency>true</latency>\n                        <timestamp>true</timestamp>\n                        <success>true</success>\n                        <label>true</label>\n                        <code>true</code>\n                        <message>true</message>\n                        <threadName>true</threadName>\n                        <dataType>true</dataType>\n                        <encoding>false</encoding>\n                        <assertions>true</assertions>\n                        <subresults>true</subresults>\n                        <responseData>false</responseData>\n                        <samplerData>false</samplerData>\n                        <xml>false</xml>\n                        <fieldNames>true</fieldNames>\n                        <responseHeaders>false</responseHeaders>\n                        <requestHeaders>false</requestHeaders>\n                        <responseDataOnError>false</responseDataOnError>\n                        <saveAssertionResultsFailureMessage>true</saveAssertionResultsFailureMessage>\n                        <assertionsResultsToSave>0</assertionsResultsToSave>\n                        <bytes>true</bytes>\n                        <sentBytes>true</sentBytes>\n                        <threadCounts>true</threadCounts>\n                        <idleTime>true</idleTime>\n                        <connectTime>true</connectTime>\n                      </value>\n                    </objProp>\n                    <stringProp name=\"filename\"></stringProp>\n                  </ResultCollector>\n                  <hashTree/>\n                </hashTree>\n                <HTTPSamplerProxy guiclass=\"HttpTestSampleGui\" testclass=\"HTTPSamplerProxy\" testname=\"Submit all and finish\" enabled=\"true\">\n                  <elementProp name=\"HTTPsampler.Arguments\" elementType=\"Arguments\" guiclass=\"HTTPArgumentsPanel\" testclass=\"Arguments\" testname=\"Argument List\" enabled=\"true\">\n                    <collectionProp name=\"Arguments.arguments\">\n                      <elementProp name=\"sesskey\" elementType=\"HTTPArgument\">\n                        <boolProp name=\"HTTPArgument.always_encode\">false</boolProp>\n                        <stringProp name=\"Argument.value\">${sesskey}</stringProp>\n                        <stringProp name=\"Argument.metadata\">=</stringProp>\n                        <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                        <stringProp name=\"Argument.name\">sesskey</stringProp>\n                      </elementProp>\n                      <elementProp name=\"slots\" elementType=\"HTTPArgument\">\n                        <boolProp name=\"HTTPArgument.always_encode\">false</boolProp>\n                        <stringProp name=\"Argument.value\"></stringProp>\n                        <stringProp name=\"Argument.metadata\">=</stringProp>\n                        <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                        <stringProp name=\"Argument.name\">slots</stringProp>\n                      </elementProp>\n                      <elementProp name=\"timeup\" elementType=\"HTTPArgument\">\n                        <boolProp name=\"HTTPArgument.always_encode\">false</boolProp>\n                        <stringProp name=\"Argument.value\">0</stringProp>\n                        <stringProp name=\"Argument.metadata\">=</stringProp>\n                        <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                        <stringProp name=\"Argument.name\">timeup</stringProp>\n                      </elementProp>\n                      <elementProp name=\"finishattempt\" elementType=\"HTTPArgument\">\n                        <boolProp name=\"HTTPArgument.always_encode\">false</boolProp>\n                        <stringProp name=\"Argument.value\">1</stringProp>\n                        <stringProp name=\"Argument.metadata\">=</stringProp>\n                        <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                        <stringProp name=\"Argument.name\">finishattempt</stringProp>\n                      </elementProp>\n                      <elementProp name=\"attempt\" elementType=\"HTTPArgument\">\n                        <boolProp name=\"HTTPArgument.always_encode\">false</boolProp>\n                        <stringProp name=\"Argument.value\">${attemptid}</stringProp>\n                        <stringProp name=\"Argument.metadata\">=</stringProp>\n                        <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                        <stringProp name=\"Argument.name\">attempt</stringProp>\n                      </elementProp>\n                    </collectionProp>\n                  </elementProp>\n                  <stringProp name=\"HTTPSampler.domain\">${host}</stringProp>\n                  <stringProp name=\"HTTPSampler.port\">${port}</stringProp>\n                  <stringProp name=\"HTTPSampler.protocol\">${protocol}</stringProp>\n                  <stringProp name=\"HTTPSampler.contentEncoding\">utf-8</stringProp>\n                  <stringProp name=\"HTTPSampler.path\">/mod/quiz/processattempt.php</stringProp>\n                  <stringProp name=\"HTTPSampler.method\">POST</stringProp>\n                  <boolProp name=\"HTTPSampler.follow_redirects\">true</boolProp>\n                  <boolProp name=\"HTTPSampler.auto_redirects\">false</boolProp>\n                  <boolProp name=\"HTTPSampler.use_keepalive\">true</boolProp>\n                  <boolProp name=\"HTTPSampler.DO_MULTIPART_POST\">true</boolProp>\n                  <stringProp name=\"HTTPSampler.embedded_url_re\"></stringProp>\n                  <stringProp name=\"HTTPSampler.implementation\">Java</stringProp>\n                  <stringProp name=\"HTTPSampler.connect_timeout\"></stringProp>\n                  <stringProp name=\"HTTPSampler.response_timeout\"></stringProp>\n                </HTTPSamplerProxy>\n                <hashTree>\n                  <ResponseAssertion guiclass=\"AssertionGui\" testclass=\"ResponseAssertion\" testname=\"Assertion - Response Contains Finish review\" enabled=\"true\">\n                    <collectionProp name=\"Asserion.test_strings\">\n                      <stringProp name=\"-1084012699\">Finish review</stringProp>\n                    </collectionProp>\n                    <intProp name=\"Assertion.test_type\">2</intProp>\n                    <stringProp name=\"Assertion.test_field\">Assertion.response_data</stringProp>\n                    <boolProp name=\"Assertion.assume_success\">false</boolProp>\n                    <stringProp name=\"Assertion.custom_message\"></stringProp>\n                  </ResponseAssertion>\n                  <hashTree/>\n                  <GaussianRandomTimer guiclass=\"GaussianRandomTimerGui\" testclass=\"GaussianRandomTimer\" testname=\"Gaussian Random Timer\" enabled=\"true\">\n                    <stringProp name=\"ConstantTimer.delay\">2000</stringProp>\n                    <stringProp name=\"RandomTimer.range\">1000</stringProp>\n                  </GaussianRandomTimer>\n                  <hashTree/>\n                  <ConstantTimer guiclass=\"ConstantTimerGui\" testclass=\"ConstantTimer\" testname=\"Constant Timer\" enabled=\"false\">\n                    <stringProp name=\"ConstantTimer.delay\">1000</stringProp>\n                  </ConstantTimer>\n                  <hashTree/>\n                  <ResultCollector guiclass=\"ViewResultsFullVisualizer\" testclass=\"ResultCollector\" testname=\"View Results Tree\" enabled=\"false\">\n                    <boolProp name=\"ResultCollector.error_logging\">false</boolProp>\n                    <objProp>\n                      <name>saveConfig</name>\n                      <value class=\"SampleSaveConfiguration\">\n                        <time>true</time>\n                        <latency>true</latency>\n                        <timestamp>true</timestamp>\n                        <success>true</success>\n                        <label>true</label>\n                        <code>true</code>\n                        <message>true</message>\n                        <threadName>true</threadName>\n                        <dataType>true</dataType>\n                        <encoding>false</encoding>\n                        <assertions>true</assertions>\n                        <subresults>true</subresults>\n                        <responseData>false</responseData>\n                        <samplerData>false</samplerData>\n                        <xml>false</xml>\n                        <fieldNames>true</fieldNames>\n                        <responseHeaders>false</responseHeaders>\n                        <requestHeaders>false</requestHeaders>\n                        <responseDataOnError>false</responseDataOnError>\n                        <saveAssertionResultsFailureMessage>true</saveAssertionResultsFailureMessage>\n                        <assertionsResultsToSave>0</assertionsResultsToSave>\n                        <bytes>true</bytes>\n                        <sentBytes>true</sentBytes>\n                        <threadCounts>true</threadCounts>\n                        <idleTime>true</idleTime>\n                        <connectTime>true</connectTime>\n                      </value>\n                    </objProp>\n                    <stringProp name=\"filename\"></stringProp>\n                  </ResultCollector>\n                  <hashTree/>\n                </hashTree>\n                <HTTPSamplerProxy guiclass=\"HttpTestSampleGui\" testclass=\"HTTPSamplerProxy\" testname=\"Click Finish Review\" enabled=\"true\">\n                  <elementProp name=\"HTTPsampler.Arguments\" elementType=\"Arguments\" guiclass=\"HTTPArgumentsPanel\" testclass=\"Arguments\" testname=\"Argument List\" enabled=\"true\">\n                    <collectionProp name=\"Arguments.arguments\">\n                      <elementProp name=\"id\" elementType=\"HTTPArgument\">\n                        <boolProp name=\"HTTPArgument.always_encode\">false</boolProp>\n                        <stringProp name=\"Argument.value\">${moduleid}</stringProp>\n                        <stringProp name=\"Argument.metadata\">=</stringProp>\n                        <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>\n                        <stringProp name=\"Argument.name\">id</stringProp>\n                      </elementProp>\n                    </collectionProp>\n                  </elementProp>\n                  <stringProp name=\"HTTPSampler.domain\">${host}</stringProp>\n                  <stringProp name=\"HTTPSampler.port\">${port}</stringProp>\n                  <stringProp name=\"HTTPSampler.protocol\">${protocol}</stringProp>\n                  <stringProp name=\"HTTPSampler.contentEncoding\"></stringProp>\n                  <stringProp name=\"HTTPSampler.path\">/mod/quiz/view.php</stringProp>\n                  <stringProp name=\"HTTPSampler.method\">GET</stringProp>\n                  <boolProp name=\"HTTPSampler.follow_redirects\">true</boolProp>\n                  <boolProp name=\"HTTPSampler.auto_redirects\">false</boolProp>\n                  <boolProp name=\"HTTPSampler.use_keepalive\">true</boolProp>\n                  <boolProp name=\"HTTPSampler.DO_MULTIPART_POST\">true</boolProp>\n                  <stringProp name=\"HTTPSampler.embedded_url_re\"></stringProp>\n                  <stringProp name=\"HTTPSampler.implementation\">Java</stringProp>\n                  <stringProp name=\"HTTPSampler.connect_timeout\"></stringProp>\n                  <stringProp name=\"HTTPSampler.response_timeout\"></stringProp>\n                </HTTPSamplerProxy>\n                <hashTree>\n                  <ResponseAssertion guiclass=\"AssertionGui\" testclass=\"ResponseAssertion\" testname=\"Assertion - Response Contains Summary of your previous atte...\" enabled=\"true\">\n                    <collectionProp name=\"Asserion.test_strings\">\n                      <stringProp name=\"1813422065\">Summary of your previous attempts</stringProp>\n                    </collectionProp>\n                    <intProp name=\"Assertion.test_type\">2</intProp>\n                    <stringProp name=\"Assertion.test_field\">Assertion.response_data</stringProp>\n                    <boolProp name=\"Assertion.assume_success\">false</boolProp>\n                    <stringProp name=\"Assertion.custom_message\"></stringProp>\n                  </ResponseAssertion>\n                  <hashTree/>\n                  <ResponseAssertion guiclass=\"AssertionGui\" testclass=\"ResponseAssertion\" testname=\"Assertion - Response Contains Highest grade\" enabled=\"true\">\n                    <collectionProp name=\"Asserion.test_strings\">\n                      <stringProp name=\"-1064949445\">Highest grade</stringProp>\n                    </collectionProp>\n                    <intProp name=\"Assertion.test_type\">2</intProp>\n                    <stringProp name=\"Assertion.test_field\">Assertion.response_data</stringProp>\n                    <boolProp name=\"Assertion.assume_success\">false</boolProp>\n                    <stringProp name=\"Assertion.custom_message\"></stringProp>\n                  </ResponseAssertion>\n                  <hashTree/>\n                  <GaussianRandomTimer guiclass=\"GaussianRandomTimerGui\" testclass=\"GaussianRandomTimer\" testname=\"Gaussian Random Timer\" enabled=\"true\">\n                    <stringProp name=\"ConstantTimer.delay\">4000</stringProp>\n                    <stringProp name=\"RandomTimer.range\">3000</stringProp>\n                  </GaussianRandomTimer>\n                  <hashTree/>\n                  <ConstantTimer guiclass=\"ConstantTimerGui\" testclass=\"ConstantTimer\" testname=\"Constant Timer\" enabled=\"false\">\n                    <stringProp name=\"ConstantTimer.delay\">1000</stringProp>\n                  </ConstantTimer>\n                  <hashTree/>\n                </hashTree>\n                <DebugSampler guiclass=\"TestBeanGUI\" testclass=\"DebugSampler\" testname=\"Debug Sampler\" enabled=\"false\">\n                  <boolProp name=\"displayJMeterProperties\">false</boolProp>\n                  <boolProp name=\"displayJMeterVariables\">true</boolProp>\n                  <boolProp name=\"displaySystemProperties\">false</boolProp>\n                </DebugSampler>\n                <hashTree/>\n                <ResultCollector guiclass=\"GraphVisualizer\" testclass=\"ResultCollector\" testname=\"Graph Results\" enabled=\"false\">\n                  <boolProp name=\"ResultCollector.error_logging\">false</boolProp>\n                  <objProp>\n                    <name>saveConfig</name>\n                    <value class=\"SampleSaveConfiguration\">\n                      <time>true</time>\n                      <latency>true</latency>\n                      <timestamp>true</timestamp>\n                      <success>true</success>\n                      <label>true</label>\n                      <code>true</code>\n                      <message>true</message>\n                      <threadName>true</threadName>\n                      <dataType>true</dataType>\n                      <encoding>false</encoding>\n                      <assertions>true</assertions>\n                      <subresults>true</subresults>\n                      <responseData>false</responseData>\n                      <samplerData>false</samplerData>\n                      <xml>true</xml>\n                      <fieldNames>false</fieldNames>\n                      <responseHeaders>false</responseHeaders>\n                      <requestHeaders>false</requestHeaders>\n                      <responseDataOnError>false</responseDataOnError>\n                      <saveAssertionResultsFailureMessage>false</saveAssertionResultsFailureMessage>\n                      <assertionsResultsToSave>0</assertionsResultsToSave>\n                      <bytes>true</bytes>\n                      <threadCounts>true</threadCounts>\n                    </value>\n                  </objProp>\n                  <stringProp name=\"filename\"></stringProp>\n                </ResultCollector>\n                <hashTree/>\n                <ResultCollector guiclass=\"ViewResultsFullVisualizer\" testclass=\"ResultCollector\" testname=\"View Results Tree\" enabled=\"false\">\n                  <boolProp name=\"ResultCollector.error_logging\">false</boolProp>\n                  <objProp>\n                    <name>saveConfig</name>\n                    <value class=\"SampleSaveConfiguration\">\n                      <time>true</time>\n                      <latency>true</latency>\n                      <timestamp>true</timestamp>\n                      <success>true</success>\n                      <label>true</label>\n                      <code>true</code>\n                      <message>true</message>\n                      <threadName>true</threadName>\n                      <dataType>true</dataType>\n                      <encoding>false</encoding>\n                      <assertions>true</assertions>\n                      <subresults>true</subresults>\n                      <responseData>false</responseData>\n                      <samplerData>false</samplerData>\n                      <xml>false</xml>\n                      <fieldNames>false</fieldNames>\n                      <responseHeaders>false</responseHeaders>\n                      <requestHeaders>false</requestHeaders>\n                      <responseDataOnError>false</responseDataOnError>\n                      <saveAssertionResultsFailureMessage>false</saveAssertionResultsFailureMessage>\n                      <assertionsResultsToSave>0</assertionsResultsToSave>\n                      <bytes>true</bytes>\n                      <threadCounts>true</threadCounts>\n                    </value>\n                  </objProp>\n                  <stringProp name=\"filename\"></stringProp>\n                </ResultCollector>\n                <hashTree/>\n                <GaussianRandomTimer guiclass=\"GaussianRandomTimerGui\" testclass=\"GaussianRandomTimer\" testname=\"Gaussian Random Timer\" enabled=\"true\">\n                  <stringProp name=\"ConstantTimer.delay\">2000</stringProp>\n                  <stringProp name=\"RandomTimer.range\">1000</stringProp>\n                </GaussianRandomTimer>\n                <hashTree/>\n              </hashTree>\n              <ResultCollector guiclass=\"ViewResultsFullVisualizer\" testclass=\"ResultCollector\" testname=\"View Results Tree\" enabled=\"true\">\n                <boolProp name=\"ResultCollector.error_logging\">false</boolProp>\n                <objProp>\n                  <name>saveConfig</name>\n                  <value class=\"SampleSaveConfiguration\">\n                    <time>true</time>\n                    <latency>true</latency>\n                    <timestamp>true</timestamp>\n                    <success>true</success>\n                    <label>true</label>\n                    <code>true</code>\n                    <message>true</message>\n                    <threadName>true</threadName>\n                    <dataType>true</dataType>\n                    <encoding>false</encoding>\n                    <assertions>true</assertions>\n                    <subresults>true</subresults>\n                    <responseData>false</responseData>\n                    <samplerData>false</samplerData>\n                    <xml>false</xml>\n                    <fieldNames>true</fieldNames>\n                    <responseHeaders>false</responseHeaders>\n                    <requestHeaders>false</requestHeaders>\n                    <responseDataOnError>false</responseDataOnError>\n                    <saveAssertionResultsFailureMessage>true</saveAssertionResultsFailureMessage>\n                    <assertionsResultsToSave>0</assertionsResultsToSave>\n                    <bytes>true</bytes>\n                    <sentBytes>true</sentBytes>\n                    <threadCounts>true</threadCounts>\n                    <idleTime>true</idleTime>\n                    <connectTime>true</connectTime>\n                  </value>\n                </objProp>\n                <stringProp name=\"filename\"></stringProp>\n              </ResultCollector>\n              <hashTree/>\n              <ResultCollector guiclass=\"TableVisualizer\" testclass=\"ResultCollector\" testname=\"View Results in Table\" enabled=\"true\">\n                <boolProp name=\"ResultCollector.error_logging\">false</boolProp>\n                <objProp>\n                  <name>saveConfig</name>\n                  <value class=\"SampleSaveConfiguration\">\n                    <time>true</time>\n                    <latency>true</latency>\n                    <timestamp>true</timestamp>\n                    <success>true</success>\n                    <label>true</label>\n                    <code>true</code>\n                    <message>true</message>\n                    <threadName>true</threadName>\n                    <dataType>true</dataType>\n                    <encoding>false</encoding>\n                    <assertions>true</assertions>\n                    <subresults>true</subresults>\n                    <responseData>false</responseData>\n                    <samplerData>false</samplerData>\n                    <xml>false</xml>\n                    <fieldNames>true</fieldNames>\n                    <responseHeaders>false</responseHeaders>\n                    <requestHeaders>false</requestHeaders>\n                    <responseDataOnError>false</responseDataOnError>\n                    <saveAssertionResultsFailureMessage>true</saveAssertionResultsFailureMessage>\n                    <assertionsResultsToSave>0</assertionsResultsToSave>\n                    <bytes>true</bytes>\n                    <sentBytes>true</sentBytes>\n                    <threadCounts>true</threadCounts>\n                    <idleTime>true</idleTime>\n                    <connectTime>true</connectTime>\n                  </value>\n                </objProp>\n                <stringProp name=\"filename\"></stringProp>\n              </ResultCollector>\n              <hashTree/>\n              <ResultCollector guiclass=\"SummaryReport\" testclass=\"ResultCollector\" testname=\"Summary Report\" enabled=\"true\">\n                <boolProp name=\"ResultCollector.error_logging\">false</boolProp>\n                <objProp>\n                  <name>saveConfig</name>\n                  <value class=\"SampleSaveConfiguration\">\n                    <time>true</time>\n                    <latency>true</latency>\n                    <timestamp>true</timestamp>\n                    <success>true</success>\n                    <label>true</label>\n                    <code>true</code>\n                    <message>true</message>\n                    <threadName>true</threadName>\n                    <dataType>true</dataType>\n                    <encoding>false</encoding>\n                    <assertions>true</assertions>\n                    <subresults>true</subresults>\n                    <responseData>false</responseData>\n                    <samplerData>false</samplerData>\n                    <xml>false</xml>\n                    <fieldNames>true</fieldNames>\n                    <responseHeaders>false</responseHeaders>\n                    <requestHeaders>false</requestHeaders>\n                    <responseDataOnError>false</responseDataOnError>\n                    <saveAssertionResultsFailureMessage>true</saveAssertionResultsFailureMessage>\n                    <assertionsResultsToSave>0</assertionsResultsToSave>\n                    <bytes>true</bytes>\n                    <sentBytes>true</sentBytes>\n                    <threadCounts>true</threadCounts>\n                    <idleTime>true</idleTime>\n                    <connectTime>true</connectTime>\n                  </value>\n                </objProp>\n                <stringProp name=\"filename\"></stringProp>\n              </ResultCollector>\n              <hashTree/>\n              <kg.apc.jmeter.vizualizers.CorrectedResultCollector guiclass=\"kg.apc.jmeter.vizualizers.HitsPerSecondGui\" testclass=\"kg.apc.jmeter.vizualizers.CorrectedResultCollector\" testname=\"jp@gc - Hits per Second\" enabled=\"true\">\n                <boolProp name=\"ResultCollector.error_logging\">false</boolProp>\n                <objProp>\n                  <name>saveConfig</name>\n                  <value class=\"SampleSaveConfiguration\">\n                    <time>true</time>\n                    <latency>true</latency>\n                    <timestamp>true</timestamp>\n                    <success>true</success>\n                    <label>true</label>\n                    <code>true</code>\n                    <message>true</message>\n                    <threadName>true</threadName>\n                    <dataType>true</dataType>\n                    <encoding>false</encoding>\n                    <assertions>true</assertions>\n                    <subresults>true</subresults>\n                    <responseData>false</responseData>\n                    <samplerData>false</samplerData>\n                    <xml>false</xml>\n                    <fieldNames>true</fieldNames>\n                    <responseHeaders>false</responseHeaders>\n                    <requestHeaders>false</requestHeaders>\n                    <responseDataOnError>false</responseDataOnError>\n                    <saveAssertionResultsFailureMessage>true</saveAssertionResultsFailureMessage>\n                    <assertionsResultsToSave>0</assertionsResultsToSave>\n                    <bytes>true</bytes>\n                    <sentBytes>true</sentBytes>\n                    <threadCounts>true</threadCounts>\n                    <idleTime>true</idleTime>\n                    <connectTime>true</connectTime>\n                  </value>\n                </objProp>\n                <stringProp name=\"filename\"></stringProp>\n                <longProp name=\"interval_grouping\">1000</longProp>\n                <boolProp name=\"graph_aggregated\">false</boolProp>\n                <stringProp name=\"include_sample_labels\"></stringProp>\n                <stringProp name=\"exclude_sample_labels\"></stringProp>\n                <stringProp name=\"start_offset\"></stringProp>\n                <stringProp name=\"end_offset\"></stringProp>\n                <boolProp name=\"include_checkbox_state\">false</boolProp>\n                <boolProp name=\"exclude_checkbox_state\">false</boolProp>\n              </kg.apc.jmeter.vizualizers.CorrectedResultCollector>\n              <hashTree/>\n              <kg.apc.jmeter.vizualizers.CorrectedResultCollector guiclass=\"kg.apc.jmeter.vizualizers.ResponseCodesPerSecondGui\" testclass=\"kg.apc.jmeter.vizualizers.CorrectedResultCollector\" testname=\"jp@gc - Response Codes per Second\" enabled=\"true\">\n                <boolProp name=\"ResultCollector.error_logging\">false</boolProp>\n                <objProp>\n                  <name>saveConfig</name>\n                  <value class=\"SampleSaveConfiguration\">\n                    <time>true</time>\n                    <latency>true</latency>\n                    <timestamp>true</timestamp>\n                    <success>true</success>\n                    <label>true</label>\n                    <code>true</code>\n                    <message>true</message>\n                    <threadName>true</threadName>\n                    <dataType>true</dataType>\n                    <encoding>false</encoding>\n                    <assertions>true</assertions>\n                    <subresults>true</subresults>\n                    <responseData>false</responseData>\n                    <samplerData>false</samplerData>\n                    <xml>false</xml>\n                    <fieldNames>true</fieldNames>\n                    <responseHeaders>false</responseHeaders>\n                    <requestHeaders>false</requestHeaders>\n                    <responseDataOnError>false</responseDataOnError>\n                    <saveAssertionResultsFailureMessage>true</saveAssertionResultsFailureMessage>\n                    <assertionsResultsToSave>0</assertionsResultsToSave>\n                    <bytes>true</bytes>\n                    <sentBytes>true</sentBytes>\n                    <threadCounts>true</threadCounts>\n                    <idleTime>true</idleTime>\n                    <connectTime>true</connectTime>\n                  </value>\n                </objProp>\n                <stringProp name=\"filename\"></stringProp>\n                <longProp name=\"interval_grouping\">1000</longProp>\n                <boolProp name=\"graph_aggregated\">false</boolProp>\n                <stringProp name=\"include_sample_labels\"></stringProp>\n                <stringProp name=\"exclude_sample_labels\"></stringProp>\n                <stringProp name=\"start_offset\"></stringProp>\n                <stringProp name=\"end_offset\"></stringProp>\n                <boolProp name=\"include_checkbox_state\">false</boolProp>\n                <boolProp name=\"exclude_checkbox_state\">false</boolProp>\n              </kg.apc.jmeter.vizualizers.CorrectedResultCollector>\n              <hashTree/>\n              <kg.apc.jmeter.vizualizers.CorrectedResultCollector guiclass=\"kg.apc.jmeter.vizualizers.LatenciesOverTimeGui\" testclass=\"kg.apc.jmeter.vizualizers.CorrectedResultCollector\" testname=\"jp@gc - Response Latencies Over Time\" enabled=\"true\">\n                <boolProp name=\"ResultCollector.error_logging\">false</boolProp>\n                <objProp>\n                  <name>saveConfig</name>\n                  <value class=\"SampleSaveConfiguration\">\n                    <time>true</time>\n                    <latency>true</latency>\n                    <timestamp>true</timestamp>\n                    <success>true</success>\n                    <label>true</label>\n                    <code>true</code>\n                    <message>true</message>\n                    <threadName>true</threadName>\n                    <dataType>true</dataType>\n                    <encoding>false</encoding>\n                    <assertions>true</assertions>\n                    <subresults>true</subresults>\n                    <responseData>false</responseData>\n                    <samplerData>false</samplerData>\n                    <xml>false</xml>\n                    <fieldNames>true</fieldNames>\n                    <responseHeaders>false</responseHeaders>\n                    <requestHeaders>false</requestHeaders>\n                    <responseDataOnError>false</responseDataOnError>\n                    <saveAssertionResultsFailureMessage>true</saveAssertionResultsFailureMessage>\n                    <assertionsResultsToSave>0</assertionsResultsToSave>\n                    <bytes>true</bytes>\n                    <sentBytes>true</sentBytes>\n                    <threadCounts>true</threadCounts>\n                    <idleTime>true</idleTime>\n                    <connectTime>true</connectTime>\n                  </value>\n                </objProp>\n                <stringProp name=\"filename\"></stringProp>\n                <longProp name=\"interval_grouping\">500</longProp>\n                <boolProp name=\"graph_aggregated\">false</boolProp>\n                <stringProp name=\"include_sample_labels\"></stringProp>\n                <stringProp name=\"exclude_sample_labels\"></stringProp>\n                <stringProp name=\"start_offset\"></stringProp>\n                <stringProp name=\"end_offset\"></stringProp>\n                <boolProp name=\"include_checkbox_state\">false</boolProp>\n                <boolProp name=\"exclude_checkbox_state\">false</boolProp>\n              </kg.apc.jmeter.vizualizers.CorrectedResultCollector>\n              <hashTree/>\n            </hashTree>\n          </hashTree>\n        </hashTree>\n      </hashTree>\n    </hashTree>\n  </hashTree>\n</jmeterTestPlan>\n"
  },
  {
    "path": "managedApplication/Cleanup.md",
    "content": "# Cleaning up a Test Deployment\n\nIf you worked through the documentation in this section you will have\ncreated a nubmer of resources and at least one entry into your Service\nCatalog. This document will explain how to remove them all.\n\n## Prerequisites\n\nWe need to ensure the [variables](Environment.md) are set up correctly.\n\n## Microsoft Entra ID\n\n``` bash\nMOODLE_MANAGED_APP_AD_ID=$(az ad group list --filter=\"displayName eq '$MOODLE_MANAGED_APP_OWNER_GROUP_NAME'\" --query [0].objectId --output tsv)\naz ad group delete --group $MOODLE_MANAGED_APP_AD_ID\n```\n\n## Remove the Service Catalog Entry\n\n``` bash\naz managedapp definition delete --resource-group $MOODLE_SERVICE_CATALOG_RG_NAME --ids $MOODLE_MANAGED_APP_ID\n```\n\n### Service catalog resource group\n\nIf you create a resource group solely for the managed application you\nare now deleting you can safely remove its resource group:\n\n``` bash\naz group delete --name $MOODLE_SERVICE_CATALOG_RG_NAME --yes\n```\n\n## Managed Application\n\nBy deleting the managed application Azure will automatically delete\nthe managed application infrastructure resource group as well (this\nwas created as part of the managed application deployment).\n\nFirst we need the application ID.\n\n``` bash\nMOODLE_DEPLOYMENT_ID=$(az managedapp show --resource-group $MOODLE_DEPLOYMENT_RG_NAME --name $MOODLE_DEPLOYMENT_NAME)\n```\n\nNow we have the ID we can delete the application.\n\n``` bash\naz managedapp delete --resource-group $MOODLE_DEPLOYMENT_RG_NAME --ids $MOODLE_DEPLOYMENT_ID\n```\n\n"
  },
  {
    "path": "managedApplication/DeployMoodleManagedApp.md",
    "content": "# Deploy a Moodle Based Managed Application into a Customer's Subscription\n\nIn this tutorial we'll demonstrate how your customers will deploy an\ninstance of your Moodle Based Managed Application in their\nsubscription.\n\n## Prerequisites\n\nIn order for the following steps to work you must first have\n[published a Moodle Based Managed Application](PublishMoodleManagedApplication.md)\ninto your service catalog.\n\n## Consume the Managed Application\n\nOnce the Moodle on Azure Managed Application is published to your\nservice catalog you can now depoloy it from within the portal or using\nthe CLI. In the following commands we'll see how to do this in the CLI.\n\n### Setup a Resource Group for the Application\n\nFirst we need to get the id of the application. This was returned in\nthe output of the command to create the service catalog entry.\nHowever, we'll use the CLI to retrieve it and record it into a\nvariable:\n\n``` bash\nMOODLE_MANAGED_APP_ID=$(az managedapp definition show --name $MOODLE_MANAGED_APP_NAME --resource-group $MOODLE_SERVICE_CATALOG_RG_NAME --query id --output tsv)\n```\n\nCreate the application resource group, this is the group in which the\ncustomer will see the managed application.\n\n``` bash\naz group create --name $MOODLE_DEPLOYMENT_RG_NAME --location=$MOODLE_DEPLOYMENT_LOCATION\n```\n\nResults:\n\n``` json\n{\n    \"id\": \"/subscriptions/325e7c34-99fb-4190-aa87-1df746c67705/resourceGroups/MoodleManagedApp\",\n    \"location\": \"southcentralus\",\n    \"managedBy\": null,\n    \"name\": \"MoodleManagedApp\",\n    \"properties\": {\n        \"provisioningState\": \"Succeeded\"\n    },\n    \"tags\": null\n}\n```\n\n### Customer Deployment\n\nWhen a customer wants to deploy an application they can do so using\neither the Portal or the CLI. In this section we'll look at how this\nis done in the CLI.\n\n#### Providing Parameters\n\nIf we were using the portal our `CreateUIDefinition.json` file would\nbe used to create a user interface to define the parameters needed in\n`mainTemplate.json`. When using the CLI we need to provide parameter\nvalues for any parameters that don't have a default. To make it easier\nto manage we'll put these parameter values into environment variables.\n\nFor convenience our `mainTemplate.json` file has defaults for all\nvalues. This means that there is no need to provide parameters in the\ncommandline, though you can override the defaults if you want to by\nadding the `--parameters` attribute. This attribute can take either\na JSON string or a filename (preceded with an '@', e.g. '--parameters @parameters.json`) containing a JSON\ndefinition for the paramters, e.g.\n\n    {\n        \"parameterName\": {\n            \"value\": \"some value\"\n        },\n        \"anotherParameterName\": {\n            \"value\": \"another value\"\n        }\n    }\n\nThe Moodle template provides sensible defaults for almost every\nparameter, the one exception to this is the SSH Public Key, used to\nprovide secure access to the VMs. For this example we will use the\ndefaults for all parameters, but we still need to create a parameters\nfile. A template file is provided here (see\n`parameters-template.json`). The following command will replace the\nplaceholder in the parameters template file with an SSH key used for\ntesting puporses (this is created as part of the envrionment setup in\nthe prerequisites):\n\n``` bash\nssh_pub_key=`cat $MOODLE_SSH_KEY_FILENAME.pub`\necho $ssh_pub_key\nsed \"s|GEN-SSH-PUB-KEY|$ssh_pub_key|g\" parameters-template.json > $MOODLE_MANAGED_APP_WORKSPACE/$MOODLE_DEPLOYMENT_NAME/parameters.json\n```\n\nIf you want to have more control over the deployment configuration\nsimply add parameters to the template file and use that to create\nparameter files for specific deployments.\n\n### Deploying the application\n\nDeploy the managed application and corresponding infrastructure.\n\n``` bash\naz managedapp create --name $MOODLE_DEPLOYMENT_NAME --location $MOODLE_DEPLOYMENT_LOCATION --kind ServiceCatalog --resource-group $MOODLE_DEPLOYMENT_RG_NAME --managedapp-definition-id $MOODLE_MANAGED_APP_ID --managed-rg-id $MOODLE_MANAGED_RG_ID --parameters @$MOODLE_MANAGED_APP_WORKSPACE/$MOODLE_DEPLOYMENT_NAME/parameters.json\n```\n"
  },
  {
    "path": "managedApplication/Environment.md",
    "content": "# Setup Environment\n\nFor convenience most of the configuration values we need to create and\nmanage our Moodle Managed Application we'll create a numer of\nEnvironment Variables. In order to store any generated files and\nconfigurations we will also create a workspace.\n\nNOTE: If you are running these scripts through SimDem you can\ncustomize these values by copying and editing `env.json` into\n`env.local.json`.\n\n## Setup for Publishing the Moodle Managed Application\n\n``` bash\nMOODLE_MANAGED_APP_OWNER_GROUP_NAME=MoodleOwner\nMOODLE_MANAGED_APP_OWNER_NICKNAME=MoodleOwner\nMOODLE_SERVICE_CATALOG_LOCATION=southcentralus\nMOODLE_SERVICE_CATALOG_RG_NAME=MoodleManagedAppServiceCatalogRG\nMOODLE_MANAGED_APP_NAME=MoodleManagedApp\nMOODLE_MANAGED_APP_LOCK_LEVEL=ReadOnly\nMOODLE_MANAGED_APP_DISPLAY_NAME=Moodle\nMOODLE_MANAGED_APP_DESCRIPTION=\"Moodle on Azure as a Managed Application\"\n```\n\n## Setup for Consuming the Moodle Managed Application\n\nCreate an id for the resource group that will be managed by the\nmanaged application provider. This is the resource group that\ninfrastructure will be deployed into. The end user does not,\ngenerally, manage this group.\n\n``` bash\nSUBSCRIPTION_ID=$(az account show --query id --output tsv)\nMOODLE_MANAGED_RG_ID=/subscriptions/$SUBSCRIPTION_ID/resourceGroups/MoodleInfrastructure\n```\n\nWe'll also need a resource group for the application deployment. This is the\nresource group into which the application is deployed. This is the resource group that\nthe provider of the managed application will have access to.\n\n``` bash\nMOODLE_DEPLOYMENT_RG_NAME=MoodleManagedAppRG\nMOODLE_DEPLOYMENT_LOCATION=southcentralus\nMOODLE_DEPLOYMENT_NAME=MoodleManagedApp\n```\n\n## Workspace\n\nWe need a workspace for storing configuration files and other\nper-deployment artifacts:\n\n``` shell\nMOODLE_MANAGED_APP_WORKSPACE=~/.moodle\nmkdir -p $MOODLE_MANAGED_APP_WORKSPACE/$MOODLE_DEPLOYMENT_NAME\n```\n\n## SSH Key\n\nWe use SSH for secure communication with our hosts. The following line\nwill check there is a valid SSH key available and, if not, create one.\n\n```\nMOODLE_SSH_KEY_FILENAME=~/.ssh/moodle_managedapp_id_rsa\nif [ ! -f \"$MOODLE_SSH_KEY_FILENAME\" ]; then ssh-keygen -t rsa -N \"\" -f $MOODLE_SSH_KEY_FILENAME; fi\n```\n"
  },
  {
    "path": "managedApplication/PublishMoodleManagedApplication.md",
    "content": "# Publish a Moodle Based Managed Appliction to Service Catalog\n\nIn this document we will look at how to publish a Moodle based Managed\nApplication into your Service Catalog so that you can allow your\ncustomers to deploy the application into their subscriptions. If you\nare not sure why you would do this you might want to read our [Moodle\nBased Managed Application Introduction](README.md) first.\n\n## Prerequisites\n\nIn the following sections we demonstrate how to use the Azure CLI to\nwork with a Moodle based Managed Application. For convenience these\ncommands use a variety of [environment variables](Environment.md) that\nshould be configured first.\n\n## Defining the Resources (mainTemplate.json)\n\nThe `mainTemplate.json` file defines the Azure resources that are\nprovisioned as part of the managed application. We've already done the\nmajority of the work here for you (see `azuredeploy.json` in the root\nof this repository). The `mainTemplate.json` file is where you\ncustomize the configuration and, optionally, add additional resources.\n\nFor the purposes of our demo we will use the ARM template from the\nroot of our project as the main tamplate.\n\nThis file is a regular [Azure Resource Manager template](https://docs.microsoft.com/en-us/azure/azure-resource-manager/resource-group-overview).\n\n## User Interface Definition (createUIDefinition.json)\n\nThe `createUIDefinition.json` file describes the user interface needed\nto configure the managed application. It defines how the user provides\ninput for each of the parameters (specified in `mainTemplate.json`).\n\nAn initial `createUIDefinition.json` file is provided in\n`managedApplication/creatueUIDefinition.json`. This files is\nsufficient to get you started building your own Moodle based Managed\nApplications.\n\nSee [Create UI Definition\ndocumentation](https://docs.microsoft.com/en-us/azure/managed-applications/create-uidefinition-overview) for more information.\n\n## Create an Microsoft Entra ID User Group or Application\n\nYou will need to create one ore more user group or appliction in\nMicrosoft Entra ID to allow you to manage the applications resources on\nbehalf of your customer. These groups or application can be given any\nbuilt-in Role-Based Access Control (RBAC) role, such as 'Owner' or\n'Contributor'. By creating more than one such group or application you\ncan configure access to your customers resources based on the specific\nneeds of each role in your organization.\n\nAzure has full documentation on [creating a group in Microsoft Entra ID](https://learn.microsoft.com/en-us/entra/fundamentals/how-to-manage-groups). The commands below will create a single 'owner' role for\nuse in the examples below.\n\nIf the Group already exists we don't want to create a new one, so we\nwill try to get the Group ID first:\n\n``` bash\nMOODLE_MANAGED_APP_AD_ID=$(az ad group list --filter=\"displayName eq '$MOODLE_MANAGED_APP_OWNER_GROUP_NAME'\" --query [0].objectId --output tsv)\n```\n\nAt this point MOODLE_MANAGED_APP_AD_ID will either be empty or it will have the ID of an existing group. If it is empty we need to create the group and grab its ID:\n\n``` bash\nif [ -z \"$MOODLE_MANAGED_APP_AD_ID\" ]; then az ad group create --display-name $MOODLE_MANAGED_APP_OWNER_GROUP_NAME --mail-nickname=$MOODLE_MANAGED_APP_OWNER_NICKNAME; fi\n```\n\nLet's ensure that we have the object ID even if we created a new one.\n\n``` bash\nMOODLE_MANAGED_APP_AD_ID=$(az ad group list --filter=\"displayName eq '$MOODLE_MANAGED_APP_OWNER_GROUP_NAME'\" --query [0].objectId --output tsv)\n```\n\nYou will also need the Role ID for your chosen role, here we will use\nthe built-in 'Owner' role:\n\n``` bash\nMOODLE_MANAGED_APP_ROLE_ID=$(az role definition list --name Owner --query [].name --output tsv)\n```\n\nThe Azure documentation has more information on how to work with [Microsoft Entra ID](https://learn.microsoft.com/en-us/azure/role-based-access-control/).\n\n## Create a Resource Group for the Managed Application Service Catalog Entry\n\n``` bash\naz group create --name $MOODLE_SERVICE_CATALOG_RG_NAME --location $MOODLE_SERVICE_CATALOG_LOCATION\n```\n\n## Publish to your Service Catalog using Azure CLI\n\nYou can publish a Managed Application definition into your Service Catalog using\nthe Azure CLI. For convenience we'll set a few environment variables\nto make it easier to work with the application. We'll need to construct\nthe authorization configuration from the app and role IDs retrieved\nearlier.\n\n``` bash\nMOODLE_MANAGED_APP_AUTHORIZATIONS=$MOODLE_MANAGED_APP_AD_ID:$MOODLE_MANAGED_APP_ROLE_ID\n```\n\nThe following command will add your managed application definition to the Service Catalog.\n\n``` bash\naz managedapp definition create --name $MOODLE_MANAGED_APP_NAME --location $MOODLE_SERVICE_CATALOG_LOCATION --resource-group $MOODLE_SERVICE_CATALOG_RG_NAME --lock-level $MOODLE_MANAGED_APP_LOCK_LEVEL --display-name $MOODLE_MANAGED_APP_DISPLAY_NAME --description \"$MOODLE_MANAGED_APP_DESCRIPTION\" --authorizations=\"$MOODLE_MANAGED_APP_AUTHORIZATIONS\" --main-template=@../azuredeploy.json --create-ui-definition=@createUIDefinition.json\n```\n\nResults:\n\n``` json\n{\n  \"artifacts\": [\n    {\n        \"name\": \"ApplicationResourceTemplate\",\n        \"type\": \"Template\",\n        \"uri\": \"https://prdsapplianceprodsn01.blob.core.windows.net/applicationdefinitions/84205_325E7C3499FB4190AA871DF746C67705_8D748DA35A5166F6BF319C41398E89D9953014D8/applicationResourceTemplate.json?sv=2014-02-14&sr=b&sig=PyYyl6dzf0vVyrde2yJZ73h6h9fqbXHwMJuXf0lGFr8%3D&se=2118-03-15T21:33:33Z&sp=r\"\n    },\n    {\n        \"name\": \"CreateUiDefinition\",\n        \"type\": \"Custom\",\n        \"uri\": \"https://management.azure.com/subscriptions/325e7c34-99fb-4190-aa87-1df746c67705/resourceGroups/MoodleManagedAppServiceCatalogRG/providers/Microsoft.Solutions/applicationDefinitions/MoodleManagedApp/applicationArtifacts/CreateUiDefinition?api-version=2017-09-01\"\n    }\n  ],\n  \"authorizations\": [\n    {\n      \"principalId\": \"fdc3f6fb-cc24-4182-9943-b63e0ed67285\",\n      \"roleDefinitionId\": \"8e3af657-a8ff-443c-a75c-2fe8c4bcb635\"\n    }\n  ],\n  \"createUiDefinition\": null,\n  \"description\": \"Moodle on Azure as a Managed Application\",\n  \"displayName\": \"Moodle\",\n  \"id\": \"/subscriptions/325e7c34-99fb-4190-aa87-1df746c67705/resourceGroups/MoodleManagedAppServiceCatalogRG/providers/Microsoft.Solutions/applicationDefinitions/MoodleManagedApp\",\n  \"identity\": null,\n  \"isEnabled\": \"True\",\n  \"location\": \"southcentralus\",\n  \"lockLevel\": \"ReadOnly\",\n  \"mainTemplate\": null,\n  \"managedBy\": null,\n  \"name\": \"MoodleManagedApp\",\n  \"packageFileUri\": null,\n  \"resourceGroup\": \"MoodleManagedAppServiceCatalogRG\",\n  \"sku\": null,\n  \"tags\": null,\n  \"type\": \"Microsoft.Solutions/applicationDefinitions\"\n}\n```\n\n### [OPTIONAL] Package the files\n\nThe `mainTemplate.json` and `createUIDefinition.json` files can be\npackaged together in a zip file. Both files should be at the root level\nof the zip. Once created the package needs to be uploaded to a location accessible\nto Azure. We've published the samples to GitHub so you can experiment\nwith minimal effort.\n\nTo use a package file remove the `--create-ui-definition` and\n`--main-tamplate` arguments from the above CLI command instead provide\na URI for the package using `--package-file-uri` argument.\n\n## Next Steps\n\nNow that you have published a Moodle based Managed Application on Azure you can:\n\n  1. [Deploy Moodle into Customer Subscription](DeployMoodleManagedApp.md)\n"
  },
  {
    "path": "managedApplication/README.md",
    "content": "# Azure Managed Application\n\n[Azure Managed\nApplications](https://docs.microsoft.com/en-us/azure/managed-applications/overview) enable you to offer your Moodle based\nsolutions to customers via the [Azure Marketplace](https://azuremarketplace.microsoft.com/en-us/marketplace/) or a Service Catalog. You define the\ninfrastructure for the solution, using the ARM templates in this\nrepository as a starting point, along with the terms for ongoing\nmanagement of the solution. The billing for your solution is handled\nthrough Azure billing.\n\n## Why the Azure Marketplace and Azure Managed Applications for Moodle Hosting Providers\nThe Azure Marketplace allows you the capability of offering an Azure-certified Moodle solution via a modern marketplace. When a customer runs Moodle from the Azure Marketplace they have the confidence that the Moodle solution certified and optimized to run on Azure, and that they can get support should they need it. \n\nUntil recently it was difficult for many Moodle hosting providers to offer Moodle via the Azure Marketplace, in particular because after a marketplace solution was deployed, customers would still be responsible for maintaining, updating, or servicing their environment. As customers are not always experts on cloud infrastructure this made offering a Marketplace offering with a Moodle-hoster backed SLA difficult.  Moreover, a customer had full-access to the resources (i.e. VMs, databases, etc.) in the solution once deployed, meaning they could easily make a change to the underlying infrastructure (such as accidentally deleting a critical VM) that might have rendered the solution unusable.  \n\nWith the advent of Azure Managed Application for the Azure Marketplace, the Moodle Hosting provider can now specify exactly which underlying infrastructure resources for a Moodle solution a customer does (and does not) have access to. This means that a Moodle hoster can now prevent a customer from make a change which could take down your Moodle solution and render your SLA void. Moreover, although customers continue to deploy your Moodle solution offering in their subscriptions just like all Azure Marketplace offerings, the customer does not have to maintain, update, or service them and troubleshooting and diagnosing of issues can be done by the Moodle hoster on-behalf of the customer.\n\n## Why Moodle Managed Applications for IT Teams?\nFor IT teams, managed applications enable you to offer pre-approved configuration of Moodle\nto users in the organization. For example, if to be compliant with organizational standards you require users deploy Moodle with certain version number, database SKUs or networking/security configurations, you can enforce compliance. \n\nRead more about [Managed\nApplications](https://docs.microsoft.com/en-us/azure/managed-applications/overview),\nor keep reading here to see how to quickly get started providing your\nown Moodle based services as Managed Applications.\n\n## Next Steps\n\n  1. [Publish a Managed Application Definition](PublishMoodleManagedApplication.md)\n  2. [Deploy a Moodle Based Managed Application](DeployMoodleManagedApp.md)\n  3. [Learn about submitting your application to the Azure Marketplace](https://docs.microsoft.com/en-us/azure/marketplace/marketplace-publishers-guide)\n  4. [Submit your application to the Azure Marketplace](https://azuremarketplace.microsoft.com/en-us/sell/nominate)\n  \n"
  },
  {
    "path": "managedApplication/createServiceCatlogUpdate.sh",
    "content": "# This script will create a Managed Application from the Azure/Moodle ARM template\n# see https://github.com/Azure/Moodle/tree/master/managedApplication\n\n# Application Configuration\n\nexport VERSION_NUMBER=1\nexport MOODLE_MANAGED_APP_DISPLAY_NAME=MoodleManagedApp\nexport MOODLE_MANAGED_APP_NAME=MoodleManagedApp_$(whoami)_$VERSION_NUMBER\nexport MOODLE_MANAGED_APP_DESCRIPTION=\"Testing the Moodle ARM template as a managed application.\"\nexport MOODLE_MANAGED_APP_OWNER_GROUP_NAME=$MOODLE_MANAGED_APP_NAME\nexport MOODLE_MANAGED_APP_OWNER_NICKNAME=$MOODLE_MANAGED_APP_NAME\nexport MOODLE_SERVICE_CATALOG_RG_NAME=Catalog_RG_$MOODLE_MANAGED_APP_NAME\nexport MOODLE_MANAGED_APP_LOCK_LEVEL=None\nexport MOODLE_SERVICE_CATALOG_LOCATION=WestUS\n\nexport PATH_TO_ARM_TEMPLATE=../azuredeploy.json\nexport PATH_TO_MOODLE_CREATEUI_DEF=createUIDefinition.json\n\n# Publish A Managed Application To Service Catalog\n\n# AD Config\n\necho \"Configuring AD\"\n\necho \"Getting Application AD ID for $MOODLE_MANAGED_APP_OWNER_GROUP_NAME\"\n\nMOODLE_MANAGED_APP_AD_ID=$(az ad group list --display-name=$MOODLE_MANAGED_APP_OWNER_GROUP_NAME --query [0].objectId --output tsv)\n\n# The following line should create a new group, if necessary, but it fails with insufficient permissions\n# if [ -z \"$MOODLE_MANAGED_APP_AD_ID\" ]; then az ad group create --display-name $MOODLE_MANAGED_APP_OWNER_GROUP_NAME --mail-nickname=$MOODLE_MANAGED_APP_OWNER_NICKNAME; fi\n# Not sure how to fix it so, for now tell user to create in portal, which works fine\nif [ -z \"$MOODLE_MANAGED_APP_AD_ID\" ]\nthen\n    echo \"AD group doesn't exist.\\n\"\n    echo \"There's a bug in the script which prevents this being automated (see comments, should be fixable by someone who knows)\\n\"\n    echo \"For now, you need to create an ad group with the name $MOODLE_MANAGED_APP_OWNER_GROUP_NAME and owner $MOODLE_MANAGED_APP_OWNER_NICKNAME see https://ms.portal.azure.com/#blade/Microsoft_AAD_IAM/GroupsManagementMenuBlade/AllGroups\"\n    read -p \"Press any key when done... \" -n1 -s;\n    echo \"Continuing...\"\n    MOODLE_MANAGED_APP_AD_ID=$(az ad group list --display-name=$MOODLE_MANAGED_APP_OWNER_GROUP_NAME --query [0].objectId --output tsv)\nfi\n\nif [ -z \"$MOODLE_MANAGED_APP_AD_ID\"]\nthen\n    >&2 echo \"Failed to get a Managed App AD ID. If you just created this it may be that it is still propogating. Rerun the script.\"\n    exit 1\nelse\n    echo \"Managed App AD ID is $MOODLE_MANAGED_APP_AD_ID\"\nfi\n\nMOODLE_MANAGED_APP_ROLE_ID=$(az role definition list --name Owner --query [].name --output tsv)\n\necho \"Managed App Role ID is $MOODLE_MANAGED_APP_ROLE_ID\"\n\n# Create a Resource Group\n\necho \"Creating the resource group for the service catalog using the name $MOODLE_SERVICE_CATALOG_RG_NAME and location $MOODLE_SERVICE_CATALOG_LOCATION\"\n\naz group create --name $MOODLE_SERVICE_CATALOG_RG_NAME --location $MOODLE_SERVICE_CATALOG_LOCATION\n\n# Publish to the Service Catalog\n\necho \"Publishing the application to the service catalog using the name $MOODLE_MANAGED_APP_NAME\"\n\nMOODLE_MANAGED_APP_AUTHORIZATIONS=$MOODLE_MANAGED_APP_AD_ID:$MOODLE_MANAGED_APP_ROLE_ID\n\naz managedapp definition create \\\n    --name $MOODLE_MANAGED_APP_NAME \\\n    --location $MOODLE_SERVICE_CATALOG_LOCATION \\\n    --resource-group $MOODLE_SERVICE_CATALOG_RG_NAME \\\n    --lock-level $MOODLE_MANAGED_APP_LOCK_LEVEL \\\n    --display-name $MOODLE_MANAGED_APP_DISPLAY_NAME \\\n    --description \"$MOODLE_MANAGED_APP_DESCRIPTION\" \\\n    --authorizations=\"$MOODLE_MANAGED_APP_AUTHORIZATIONS\" \\\n    --main-template=@$PATH_TO_ARM_TEMPLATE \\\n    --create-ui-definition=@$PATH_TO_MOODLE_CREATEUI_DEF\n\n\nMOODLE_MANAGED_APP_ID=$(az managedapp definition show --name $MOODLE_MANAGED_APP_NAME --resource-group $MOODLE_SERVICE_CATALOG_RG_NAME --query id --output tsv)\n\necho\necho \"###############################################################\"\necho \"Assuming no errors reporteed above, you can now deploy an application in the portal at https://ms.portal.azure.com/#blade/HubsExtension/BrowseResource/resourceType/Microsoft.Solutions%2FapplicationDefinitions\"\necho \"###############################################################\"\n"
  },
  {
    "path": "managedApplication/createUIDefinition.json",
    "content": "{\n    \"$schema\": \"https://schema.management.azure.com/schemas/0.1.2-preview/CreateUIDefinition.MultiVm.json#\",\n    \"handler\": \"Microsoft.Compute.MultiVm\",\n    \"version\": \"0.1.2-preview\",\n    \"parameters\": {\n        \"basics\": [\n            {}\n        ],\n        \"steps\": [\n            {\n                \"name\": \"credentialsConfig\",\n                \"label\": \"VM Credentials\",\n                \"subLabel\": {\n                    \"preValidation\": \"Provide credentials for accessing the VMs in your application.\",\n                    \"postValidation\": \"Credentials configured.\"\n                },\n                \"bladeTitle\": \"Credentials\",\n                \"elements\": [\n                    {\n                        \"name\": \"sshPublicKey\",\n                        \"type\": \"Microsoft.Common.TextBox\",\n                        \"label\": \"SSH Public Key\",\n                        \"toolTip\": \"Public SSH Key to use for access\",\n                        \"constraints\": {\n                            \"required\": true\n                        }\n                    }\n                ]\n            }\n        ],\n        \"outputs\": {\n            \"sshPublicKey\": \"[steps('credentialsConfig').sshPublicKey]\"\n        }\n    }\n}\n\n        \n\n                \n"
  },
  {
    "path": "managedApplication/parameters-template.json",
    "content": "{\n    \"sshPublicKey\":         { \"value\": \"GEN-SSH-PUB-KEY\" },\n    \"redisDeploySwitch\":    { \"value\": false },\n    \"dbServerType\":         { \"value\": \"mysql\" },\n    \"fileServerType\":       { \"value\": \"nfs\" },\n    \"autoscaleVmSku\":       { \"value\": \"Standard_DS1_v2\" },\n    \"fileServerDiskCount\":  { \"value\": 2 }\n}\n"
  },
  {
    "path": "metadata.json",
    "content": "{\n  \"itemDisplayName\": \"Autoscalable Moodle on Azure\",\n  \"description\": \"Deploys an autoscaling Moodle cluster with configurable objectfs storage, Azure redis, Azure MySQL/Postgres/MSSQu, and Elasticsearch. Can be configured for very small or very large sites. Deploys frontend components to a private network with a jumphost to access nodes. Requires keyed SSH access.\",\n  \"summary\": \"Moodle autoscale with redis/db/elasticsearch\",\n  \"githubUsername\": \"hosungsmsft\",\n  \"dateUpdated\": \"2018-04-20\"\n}\n"
  },
  {
    "path": "migration/azure-fileshare-sa-deploy.json",
    "content": "{\n    \"$schema\": \"https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#\",\n    \"contentVersion\": \"1.0.0.0\",\n    \"parameters\": {\n        \"storageAccountType\": {\n            \"defaultValue\": \"Premium_LRS\",\n            \"allowedValues\": [\n                \"Standard_LRS\",\n                \"Standard_GRS\",\n                \"Standard_ZRS\",\n                \"Premium_LRS\"\n            ],\n            \"metadata\": {\n                \"description\": \"Storage Account type.\"\n            },\n            \"type\": \"string\"\n        },\n        \"fileServerDiskSize\": {\n            \"defaultValue\": \"1024\",\n            \"metadata\": {\n                \"description\": \"Size of the azure file share in GB.\"\n            },\n            \"type\": \"string\"\n        }\n    },\n    \"resources\": [\n        {\n            \"type\": \"Microsoft.Resources/deployments\",\n            \"apiVersion\": \"2017-05-10\",\n            \"name\": \"storageAccountTemplate\",\n            \"properties\": {\n                \"mode\": \"Incremental\",\n                \"parameters\": {\n                    \"moodleCommon\": {\n                        \"value\": \"[variables('moodleCommon')]\"\n                    }\n                },\n                \"templateLink\": {\n                    \"uri\": \"[concat(variables('moodleCommon').baseTemplateUrl,'storageAccount.json',variables('moodleCommon').artifactsSasToken)]\"\n                }\n            }\n        },\n        {\n            \"type\": \"Microsoft.Storage/storageAccounts/fileServices/shares\",\n            \"apiVersion\": \"2019-06-01\",\n            \"name\": \"[concat(variables('storageName'), '/default/moodle')]\",\n            \"dependsOn\": [\n                \"Microsoft.Resources/deployments/storageAccountTemplate\"\n            ],\n            \"properties\": {\n                \"accessTier\": \"[if(equals(variables('moodleCommon').storageAccountType, 'Premium_LRS'), 'Premium', 'TransactionOptimized')]\",\n                \"shareQuota\": \"[int(variables('moodleCommon').fileServerDiskSize)]\",\n                \"enabledProtocols\": \"SMB\"\n            }\n        }\n    ],\n    \"outputs\": {\n        \"storageAccountName\": {\n            \"type\": \"string\",\n            \"value\": \"[variables('moodleCommon').storageAccountName]\"\n        },\n        \"storageAccountType\": {\n            \"type\": \"string\",\n            \"value\": \"[variables('moodleCommon').storageAccountType]\"\n        },\n        \"fileServerDiskSize\": {\n            \"type\": \"string\",\n            \"value\": \"[variables('moodleCommon').fileServerDiskSize]\"\n        }\n    },\n    \"variables\": {\n        \"_artifactsLocation\": \"https://raw.githubusercontent.com/Azure/Moodle/master/\",\n        \"_artifactsLocationSasToken\": \"\",\n        \"unsupportedLocations\": [\n            \"eastus2euap\",\n            \"westcentralus\"\n        ],\n        \"documentationLine1\": \"Some of the Azure Services used by moodle migration are not available in few regions. Those regions are declared above.\",\n        \"documentationLine2\": \"If resource group belong to one of those unsupported regions, then use default region 'westus' for deployment.\",\n        \"rgLocation\": \"[toLower(resourceGroup().location)]\",\n        \"location\": \"[if(contains(variables('unsupportedLocations'), variables('rgLocation')), 'westus', variables('rgLocation'))]\",\n        \"moodleCommon\": {\n            \"baseTemplateUrl\": \"[concat(variables('_artifactsLocation'), 'nested/')]\",\n            \"artifactsSasToken\": \"[variables('_artifactsLocationSasToken')]\",\n            \"fileServerType\": \"azurefiles\",\n            \"location\": \"[variables('location')]\",\n            \"storageAccountName\": \"[tolower(concat('abs',variables('resourceprefix')))]\",\n            \"storageAccountType\": \"[parameters('storageAccountType')]\",\n            \"fileServerDiskSize\": \"[parameters('fileServerDiskSize')]\"\n        },\n        \"resourceprefix\": \"[substring(uniqueString(resourceGroup().id, 'mainTemplate'), 3, 6)]\",\n        \"storageName\": \"[concat(variables('moodleCommon').storageAccountName,if(equals(variables('moodleCommon').fileServerType, 'azurefiles'), 'af', 'naf'))]\"\n    }\n}\n"
  },
  {
    "path": "migration/azuredeploy-migration.json",
    "content": "{\n    \"$schema\": \"https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#\",\n    \"contentVersion\": \"1.0.0.0\",\n    \"parameters\": {\n        \"redisDeploySwitch\": {\n            \"defaultValue\": \"false\",\n            \"metadata\": {\n                \"description\": \"Switch to deploy a redis cache or not. Note that certain versions of Moodle (e.g., 3.1) don't work well with Redis, so use this only for known well-working Moodle versions (e.g., 3.4).\"\n            },\n            \"allowedValues\": [ \n                \"true\", \n                \"false\" \n            ],\n            \"type\": \"string\"\n        },\n        \"httpsTermination\": {\n            \"allowedValues\": [\n                \"VMSS\",\n                \"AppGw\",\n                \"None\"\n            ],\n            \"defaultValue\": \"VMSS\",\n            \"metadata\": {\n                \"description\": \"Indicates where https termination occurs. 'VMSS' is for https termination at the VMSS instance VMs (using nginx https proxy). 'AppGw' is for https termination with an Azure Application Gateway. When selecting this, you need to specify all appGw* parameters. 'None' is for testing only with no https. 'None' may not be used with a separately configured https termination layer. If you want to use the 'None' option with your separately configured https termination layer, you'll need to update your Moodle config.php manually for $cfg->wwwroot and $cfg->sslproxy.\"\n            },\n            \"type\": \"string\"\n        },\n        \"loadBalancerSku\": {\n            \"defaultValue\": \"Standard\",\n            \"allowedValues\": [\n                \"Basic\",\n                \"Standard\"\n            ],\n            \"metadata\": {\n                \"description\": \"Loadbalancer SKU\"\n            },\n            \"type\": \"string\"\n        },\n        \"moodleVersion\": {\n            \"allowedValues\": [\n                \"MOODLE_39_STABLE\",\n                \"MOODLE_38_STABLE\"\n            ],\n            \"defaultValue\": \"MOODLE_38_STABLE\",\n            \"metadata\": {\n                \"description\": \"The Moodle version you want to install.\"\n            },\n            \"type\": \"string\"\n        },\n        \"sshPublicKey\": {\n            \"metadata\": {\n                \"description\": \"ssh public key\"\n            },\n            \"type\": \"string\"\n        },\n        \"webServerType\": {\n            \"defaultValue\": \"nginx\",\n            \"allowedValues\": [\n                \"apache\",\n                \"nginx\"\n            ],\n            \"metadata\": {\n                \"description\": \"Web server type\"\n            },\n            \"type\": \"string\"\n        },\n        \"autoscaleVmSku\": {\t\n            \"defaultValue\": \"Standard_DS2_v2\",\t\n            \"metadata\": {\t\n                \"description\": \"VM size for autoscaled web VMs\"\t\n            },\t\n            \"type\": \"string\"\t\n        },\t\n        \"autoscaleVmCountMax\": {\t\n            \"defaultValue\": \"10\",\t\n            \"metadata\": {\t\n                \"description\": \"Maximum number of autoscaled web VMs\"\t\n            },\t\n            \"type\": \"string\"\t\n        },\n        \"phpVersion\": {\n            \"allowedValues\": [\n                \"7.2\",\n                \"7.3\",\n                \"7.4\"\n            ],\n            \"defaultValue\": \"7.4\",\n            \"metadata\": {\n                \"description\": \"php version\"\n            },\n            \"type\": \"string\"\n        },\n        \"dbServerType\": {\n            \"defaultValue\": \"mysql\",\n            \"allowedValues\": [\n                \"postgres\",\n                \"mysql\"\n            ],\n            \"metadata\": {\n                \"description\": \"Database type\"\n            },\n            \"type\": \"string\"\n        },\n        \"moodleDbName\": {\n            \"defaultValue\": \"moodle\",\n            \"metadata\": {\n                \"description\": \"Moodle Database name\"\n            },\n            \"type\": \"string\"\n        },\n        \"moodleDbUser\": {\n            \"defaultValue\": \"moodle\",\n            \"metadata\": {\n                \"description\": \"Moodle Database username. This user is different from Database admin user.\"\n            },\n            \"type\": \"string\"\n        },\n        \"mysqlPgresVcores\": {\n            \"allowedValues\": [\n                \"1\",\n                \"2\",\n                \"4\",\n                \"8\",\n                \"16\",\n                \"32\"\n            ],\n            \"defaultValue\": \"2\",\n            \"metadata\": {\n                \"description\": \"MySql/Postgresql vCores. For Basic tier, only 1 & 2 are allowed. For GeneralPurpose tier, 2, 4, 8, 16, 32 are allowed. For MemoryOptimized, 2, 4, 8, 16 are allowed.\"\n            },\n            \"type\": \"string\"\n        },\n        \"mysqlPgresStgSizeGB\": {\n            \"defaultValue\": \"125\",\n            \"metadata\": {\n                \"description\": \"MySql/Postgresql storage size in GB. Minimum 5GB, increase by 1GB, up to 1TB (1024 GB)\"\n            },\n            \"type\": \"string\"\n        },\n        \"mysqlPgresSkuTier\": {\n            \"allowedValues\": [\n                \"Basic\",\n                \"GeneralPurpose\",\n                \"MemoryOptimized\"\n            ],\n            \"defaultValue\": \"GeneralPurpose\",\n            \"metadata\": {\n                \"description\": \"MySql/Postgresql sku tier\"\n            },\n            \"type\": \"string\"\n        },\n        \"mysqlPgresSkuHwFamily\": {\n            \"allowedValues\": [\n                \"Gen4\",\n                \"Gen5\"\n            ],\n            \"defaultValue\": \"Gen5\",\n            \"metadata\": {\n                \"description\": \"MySql/Postgresql sku hardware family. Central US is Gen4 only, so make sure to change this parameter to Gen4 if your deployment is on Central US.\"\n            },\n            \"type\": \"string\"\n        },\n        \"mysqlVersion\": {\n            \"allowedValues\": [\n                \"5.6\",\n                \"5.7\"\n            ],\n            \"defaultValue\": \"5.7\",\n            \"metadata\": {\n                \"description\": \"Mysql version\"\n            },\n            \"type\": \"string\"\n        },\n        \"fileServerDiskSize\": {\n            \"defaultValue\": \"1024\",\n            \"metadata\": {\n                \"description\": \"Size per disk for gluster nodes or nfs server\"\n            },\n            \"type\": \"string\"\n        },\n        \"vNetAddressSpace\": {\n            \"defaultValue\": \"172.31.0.0\",\n            \"metadata\": {\n                \"description\": \"Address range for the Moodle virtual network and various subnets - presumed /16 for a newly created vnet in case customVnetId is blank. Further subneting (a number of */24 subnets starting from the xxx.yyy.zzz.0/24 will be created on a newly created vnet or your BYO-vnet (specified in customVnetId parameter).\"\n            },\n            \"type\": \"string\"\n        },\n        \"ubuntuVersion\": {\n            \"type\": \"string\",\n            \"allowedValues\": [\n                \"18.04-LTS\"\n            ],\n            \"defaultValue\": \"18.04-LTS\"\n        }\n    },\n    \"variables\": {\n        \"_artifactsLocation\": \"https://raw.githubusercontent.com/Azure/Moodle/master/\",\n        \"_artifactsLocationSasToken\": \"\",\n        \"unsupportedLocations\": [\n            \"eastus2euap\",\n            \"westcentralus\"\n        ],\n        \"documentationLine1\": \"Some of the Azure Services used by moodle migration are not available in few regions. Those regions are declared above.\",\n        \"documentationLine2\": \"If resource group belong to one of those unsupported regions, then use default region 'westus' for deployment.\",\n        \"rgLocation\": \"[toLower(resourceGroup().location)]\",\n        \"location\": \"[if(contains(variables('unsupportedLocations'), variables('rgLocation')), 'westus', variables('rgLocation'))]\"\n    },\n    \"resources\": [\n        {\n            \"type\": \"Microsoft.Resources/deployments\",\n            \"apiVersion\": \"2017-05-10\",\n            \"name\": \"mainTemplate\",\n            \"properties\": {\n                \"mode\": \"Incremental\",\n                \"parameters\": {\n                    \"_artifactsLocation\":           { \"value\": \"[variables('_artifactsLocation')]\" },\n                    \"_artifactsLocationSasToken\":   { \"value\": \"[variables('_artifactsLocationSasToken')]\" },\n                    \"redisDeploySwitch\":            { \"value\": \"[bool(parameters('redisDeploySwitch'))]\" },\n                    \"httpsTermination\":             { \"value\": \"[parameters('httpsTermination')]\"},\n                    \"loadBalancerSku\":              { \"value\": \"[parameters('loadBalancerSku')]\"},\n                    \"siteURL\":                      { \"value\": \"\"},\n                    \"moodleVersion\":                { \"value\": \"[parameters('moodleVersion')]\"},\n                    \"sshPublicKey\":                 { \"value\": \"[parameters('sshPublicKey')]\" },\n                    \"controllerVmSku\":              { \"value\": \"Standard_DS1_v2\"},\n                    \"webServerType\":                { \"value\": \"[parameters('webServerType')]\"},\n                    \"autoscaleVmSku\":               { \"value\": \"[parameters('autoscaleVmSku')]\" },\n                    \"autoscaleVmCountMax\":          { \"value\": \"[int(parameters('autoscaleVmCountMax'))]\" },\n                    \"phpVersion\":                   { \"value\": \"[parameters('phpVersion')]\" },\n                    \"dbServerType\":                 { \"value\": \"[parameters('dbServerType')]\" },\n                    \"dbLogin\":                      { \"value\": \"dbadmin\" },\n                    \"moodleDbName\":                 { \"value\": \"[parameters('moodleDbName')]\" },\n                    \"moodleDbUser\":                 { \"value\": \"[parameters('moodleDbUser')]\" },\n                    \"mysqlPgresVcores\":             { \"value\": \"[int(parameters('mysqlPgresVcores'))]\" },\n                    \"mysqlPgresStgSizeGB\":          { \"value\": \"[int(parameters('mysqlPgresStgSizeGB'))]\" },\n                    \"mysqlPgresSkuTier\":            { \"value\": \"[parameters('mysqlPgresSkuTier')]\" },\n                    \"mysqlPgresSkuHwFamily\":        { \"value\": \"[parameters('mysqlPgresSkuHwFamily')]\" },\n                    \"mysqlVersion\":                 { \"value\": \"[parameters('mysqlVersion')]\" },\n                    \"postgresVersion\":              { \"value\": \"9.6\" },\n                    \"fileServerType\":               { \"value\": \"azurefiles\" },\n                    \"storageAccountType\":           { \"value\": \"Premium_LRS\" },\n                    \"fileServerDiskSize\":           { \"value\": \"[int(parameters('fileServerDiskSize'))]\" },\n                    \"searchType\":                   { \"value\": \"none\" },\n                    \"azureSearchSku\":               { \"value\": \"basic\" },\n                    \"vNetAddressSpace\":             { \"value\": \"[parameters('vNetAddressSpace')]\"},\n                    \"ubuntuVersion\":                { \"value\": \"[parameters('ubuntuVersion')]\"},\n                    \"location\":                     { \"value\": \"[variables('location')]\" },\n                    \"enableAccelNwForOtherVmsSwitch\":   { \"value\" : false },\n                    \"isMigration\":                  { \"value\": true }\n                },\n                \"templateLink\": {\n                    \"uri\": \"[concat(variables('_artifactsLocation'), 'azuredeploy.json', variables('_artifactsLocationSasToken'))]\"\n                }\n            }\n        }\n    ],\n    \"outputs\": {\n        \"mainTemplateOutputs\": {\n            \"type\": \"object\",\n            \"value\": \"[reference('mainTemplate').outputs]\"\n        }\n    }\n}\n"
  },
  {
    "path": "nested/appgw.json",
    "content": "{\n    \"$schema\": \"http://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json\",\n    \"contentVersion\": \"1.0.0.0\",\n    \"parameters\": {\n        \"moodleCommon\": {\n            \"metadata\": {\n                \"description\": \"Common Moodle values\"\n            },\n            \"type\": \"object\"\n        },\n        \"subnetIdAppGw\": {\n            \"metadata\": {\n                \"description\": \"Azure resource ID of the subnet where this App Gw should be deployed\"\n            },\n            \"type\": \"string\"\n        },\n        \"sslCertData\": {\n            \"metadata\": {\n                \"description\": \"Base64-encoded PFX (no password protected) file content for the SSL cert to be used on the App Gateway for SSL termination. Should be passed from an Azure Key Vault.\"\n            },\n            \"type\": \"securestring\"\n        }\n    },\n    \"resources\": [\n        {\n            \"type\": \"Microsoft.Network/applicationGateways\",\n            \"name\": \"[parameters('moodleCommon').appGwName]\",\n            \"location\": \"[parameters('moodleCommon').location]\",\n            \"apiVersion\": \"2019-11-01\",\n            \"properties\": {\n                \"sku\": {\n                    \"name\": \"[parameters('moodleCommon').appGwSkuName]\",\n                    \"tier\": \"[parameters('moodleCommon').appGwSkuTier]\",\n                    \"capacity\": \"[parameters('moodleCommon').appGwSkuCapacity]\"\n                },\n                \"gatewayIPConfigurations\": [\n                    {\n                        \"name\": \"appGwIpConfig\",\n                        \"properties\": {\n                            \"subnet\": {\n                                \"id\": \"[parameters('subnetIdAppGw')]\"\n                            }\n                        }\n                    }\n                ],\n                \"frontendIPConfigurations\": [\n                    {\n                        \"name\": \"appGwFrontendIP\",\n                        \"properties\": {\n                            \"publicIPAddress\": {\n                                \"id\": \"[variables('appGwPublicIPAddressID')]\"\n                            }\n                        }\n                    }\n                ],\n                \"frontendPorts\": [\n                    {\n                        \"name\": \"httpsFrontendPort\",\n                        \"properties\": {\n                            \"port\": 443\n                        }\n                    },\n                    {\n                        \"name\": \"httpFrontendPort\",\n                        \"properties\": {\n                            \"port\": 80\n                        }\n                    }                    \n                ],\n                \"backendAddressPools\": [\n                    {\n                        \"name\": \"[variables('appGwBePoolName')]\"\n                    }\n                ],\n                \"backendHttpSettingsCollection\": [\n                    {\n                        \"name\": \"appGwBackendHttpSettings\",\n                        \"properties\": {\n                            \"port\": 80,\n                            \"protocol\": \"Http\",\n                            \"cookieBasedAffinity\": \"Disabled\"\n                        }\n                    }\n                ],\n                \"sslCertificates\": [\n                    {\n                        \"name\": \"appGatewaySslCert\",\n                        \"properties\": {\n                            \"data\": \"[parameters('sslCertData')]\"\n                        }\n                    }\n                ],\n                \"httpListeners\": [\n                    {\n                        \"name\": \"appGwHttpsListener\",\n                        \"properties\": {\n                            \"frontendIPConfiguration\": {\n                                \"Id\": \"[concat(variables('appGwID'), '/frontendIPConfigurations/appGwFrontendIP')]\"\n                            },\n                            \"frontendPort\": {\n                                \"Id\": \"[concat(variables('appGwID'), '/frontendPorts/httpsFrontendPort')]\"\n                            },\n                            \"protocol\": \"Https\",\n                            \"sslCertificate\": {\n                                \"Id\": \"[concat(variables('appGwID'), '/sslCertificates/appGatewaySslCert')]\"\n                            }\n                        }\n                    },\n                    {\n                        \"name\": \"appGwHttpListener\",\n                        \"properties\": {\n                            \"frontendIPConfiguration\": {\n                                \"Id\": \"[concat(variables('appGwID'), '/frontendIPConfigurations/appGwFrontendIP')]\"\n                            },\n                            \"frontendPort\": {\n                                \"Id\": \"[concat(variables('appGwID'), '/frontendPorts/httpFrontendPort')]\"\n                            },\n                            \"protocol\": \"Http\"\n                        }\n                    }\n                ],\n                \"redirectConfigurations\": [\n                    {\n                        \"name\": \"httpToHttps\",\n                        \"properties\": {\n                            \"redirectType\":\"Permanent\",\n                            \"includePath\" : true,\n                            \"includeQueryString\" : true,\n                            \"targetListener\": {\n                                \"id\": \"[concat(variables('appGwID'), '/httpListeners/appGwHttpsListener')]\"\n                            }\n                        }\n                    }\n                ],                \n                \"requestRoutingRules\": [\n                    {\n                        \"Name\": \"httpsRule\",\n                        \"properties\": {\n                            \"ruleType\": \"Basic\",\n                            \"httpListener\": {\n                                \"id\": \"[concat(variables('appGwID'), '/httpListeners/appGwHttpsListener')]\"\n                            },\n                            \"backendAddressPool\": {\n                                \"id\": \"[concat(variables('appGwID'), '/backendAddressPools/', variables('appGwBePoolName'))]\"\n                            },\n                            \"backendHttpSettings\": {\n                                \"id\": \"[concat(variables('appGwID'), '/backendHttpSettingsCollection/appGwBackendHttpSettings')]\"\n                            }\n                        }\n                    },\n                    {\n                        \"Name\": \"httpRedirectRule\",\n                        \"properties\": {\n                            \"ruleType\": \"Basic\",\n                            \"httpListener\": {\n                                \"id\": \"[concat(variables('appGwID'), '/httpListeners/appGwHttpListener')]\"\n                            },\n                            \"redirectConfiguration\": {\n                                \"id\": \"[concat(variables('appGwID'), '/redirectConfigurations/httpToHttps')]\"\n                            }\n                        }\n                    }\n                ]\n            }\n        }\n    ],\n    \"variables\": {\n        \"documentation1\": \"This sub-template creates an Azure Application Gateway for SSL offloading. It expects certain values in the 'common' datastructure.\",\n        \"appGwBePoolName\": \"[parameters('moodleCommon').appGwBePoolName]\",\n        \"appGwPublicIPAddressID\": \"[resourceId('Microsoft.Network/publicIPAddresses',parameters('moodleCommon').appGwPipName)]\",\n        \"appGwID\": \"[resourceId('Microsoft.Network/applicationGateways', parameters('moodleCommon').appGwName)]\"\n    }\n}"
  },
  {
    "path": "nested/controller.json",
    "content": "{\n    \"$schema\": \"http://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#\",\n    \"contentVersion\": \"1.0.0.0\",\n    \"parameters\": {\n        \"moodleCommon\": {\n            \"metadata\": {\n                \"description\": \"Common Moodle values\"\n            },\n            \"type\": \"object\"\n        },\n        \"subnetIdWeb\": {\n            \"metadata\": {\n                \"description\": \"Azure resource ID of the subnet where this VM is to be deployed\"\n            },\n            \"type\": \"string\"\n        },\n        \"ctlrPubIpId\": {\n            \"metadata\": {\n                \"description\": \"Resource ID of the controller VM public IP address\"\n            },\n            \"type\": \"string\"\n        },\n        \"vmSetupParamsObj\": {\n            \"metadata\": {\n                \"description\": \"JSON-structured VM setup params that'll be injected to the VM (through cloud-init) and used by the custom script (install_moodle.sh)\"\n            },\n            \"type\": \"object\"\n        }\n    },\n    \"resources\": [\n        {\n            \"type\": \"Microsoft.Network/networkSecurityGroups\",\n            \"apiVersion\": \"2017-10-01\",\n            \"location\": \"[parameters('moodleCommon').location]\",\n            \"name\": \"[parameters('moodleCommon').ctlrNsgName]\",\n            \"properties\": {\n                \"securityRules\": [\n                    {\n                        \"name\": \"Allow_SSH\",\n                        \"properties\": {\n                            \"access\": \"Allow\",\n                            \"destinationAddressPrefix\": \"*\",\n                            \"destinationPortRange\": \"22\",\n                            \"direction\": \"Inbound\",\n                            \"priority\": 1000,\n                            \"protocol\": \"Tcp\",\n                            \"sourceAddressPrefix\": \"*\",\n                            \"sourcePortRange\": \"*\"\n                        }\n                    },\n                    {\n                        \"name\": \"Allow_http\",\n                        \"properties\": {\n                            \"access\": \"Allow\",\n                            \"destinationAddressPrefix\": \"*\",\n                            \"destinationPortRange\": \"80\",\n                            \"direction\": \"Inbound\",\n                            \"priority\": 1005,\n                            \"protocol\": \"Tcp\",\n                            \"sourceAddressPrefix\": \"*\",\n                            \"sourcePortRange\": \"*\"\n                        }\n                    }\n                ]\n            },\n            \"tags\": {\n                \"displayName\": \"Controller NSG\"\n            }\n        },\n        {\n            \"type\": \"Microsoft.Network/networkInterfaces\",\n            \"apiVersion\": \"2017-10-01\",\n            \"dependsOn\": [\n                \"[concat('Microsoft.Network/networkSecurityGroups/', parameters('moodleCommon').ctlrNsgName)]\"\n            ],\n            \"location\": \"[parameters('moodleCommon').location]\",\n            \"name\": \"[parameters('moodleCommon').ctlrNicName]\",\n            \"properties\": {\n                \"networkSecurityGroup\": {\n                    \"id\": \"[variables('nsgRef')]\"\n                },\n                \"ipConfigurations\": [\n                    {\n                        \"name\": \"ipcfgctlr\",\n                        \"properties\": {\n                            \"privateIPAllocationMethod\": \"Dynamic\",\n                            \"publicIPAddress\": {\n                                \"id\": \"[parameters('ctlrPubIpId')]\"\n                            },\n                            \"subnet\": {\n                                \"id\": \"[parameters('subnetIdWeb')]\"\n                            }\n                        }\n                    }\n                ],\n                \"enableAcceleratedNetworking\": \"[parameters('moodleCommon').enableAccelNwForCtlrVmSwitch]\"\n            },\n            \"tags\": {\n                \"displayName\": \"ctlrNic\"\n            }\n        },\n        {\n            \"type\": \"Microsoft.Compute/virtualMachines\",\n            \"apiVersion\": \"2017-03-30\",\n            \"dependsOn\": [\n                \"[concat('Microsoft.Network/networkInterfaces/', parameters('moodleCommon').ctlrNicName)]\"\n            ],\n            \"location\": \"[parameters('moodleCommon').location]\",\n            \"name\": \"[parameters('moodleCommon').ctlrVmName]\",\n            \"properties\": {\n                \"hardwareProfile\": {\n                    \"vmSize\": \"[parameters('moodleCommon').controllerVmSku]\"\n                },\n                \"networkProfile\": {\n                    \"networkInterfaces\": [\n                        {\n                            \"id\": \"[variables('nicRef')]\"\n                        }\n                    ]\n                },\n                \"osProfile\": {\n                    \"adminUsername\": \"[parameters('moodleCommon').sshUsername]\",\n                    \"computerName\": \"[parameters('moodleCommon').ctlrVmName]\",\n                    \"secrets\": \"[parameters('moodleCommon').ctlrVmSecrets]\",\n                    \"customData\": \"[base64(concat('#cloud-config\\nwrite_files:\\n- encoding: b64\\n  content: ', base64(string(parameters('vmSetupParamsObj'))), '\\n  owner: root:root\\n  path: ', parameters('moodleCommon').moodleOnAzureConfigsJsonPath, '\\n  permissions: ', variables('singleQuote'), '0400', variables('singleQuote')))]\",\n                    \"linuxConfiguration\": {\n                        \"disablePasswordAuthentication\": true,\n                        \"ssh\": {\n                            \"publicKeys\": [\n                                {\n                                    \"path\": \"[concat('/home/', parameters('moodleCommon').sshUsername, '/.ssh/authorized_keys')]\",\n                                    \"keyData\": \"[parameters('moodleCommon').sshPublicKey]\"\n                                }\n                            ]\n                        }\n                    }\n                },\n                \"storageProfile\": {\n                    \"imageReference\": \"[parameters('moodleCommon').osType]\",\n                    \"osDisk\": {\n                        \"createOption\": \"FromImage\",\n                        \"managedDisk\": {\n                            \"storageAccountType\": \"[parameters('moodleCommon').osDiskStorageType]\"\n                        },\n                        \"name\": \"[parameters('moodleCommon').ctlrVmName]\"\n                    },\n                    \"dataDisks\": \"[take(variables('nfsDiskArray'),if(equals(parameters('moodleCommon').fileServerType,'nfs'), parameters('moodleCommon').fileServerDiskCount, 0))]\"\n                }\n            },\n            \"tags\": {\n                \"displayName\": \"Controller Virtual Machine\"\n            }\n        },\n        {\n            \"condition\": \"[parameters('moodleCommon').applyScriptsSwitch]\",\n            \"type\": \"Microsoft.Resources/deployments\",\n            \"apiVersion\": \"2017-05-10\",\n            \"dependsOn\": [\n                \"[concat('Microsoft.Compute/virtualMachines/', parameters('moodleCommon').ctlrVmName)]\"\n            ],\n            \"name\": \"[concat(parameters('moodleCommon').ctlrVmName,'-ScriptProcessor')]\",\n            \"properties\": {\n                \"mode\": \"Incremental\",\n                \"parameters\": {\n                    \"moodleCommon\": {\n                        \"value\": \"[parameters('moodleCommon')]\"\n                    }\n                },\n                \"templateLink\": {\n                    \"uri\": \"[concat(parameters('moodleCommon').baseTemplateUrl, 'controllersetup.json', parameters('moodleCommon').artifactsSasToken)]\"\n                }\n            }\n        },\n        {\n            \"condition\": \"[parameters('moodleCommon').azureBackupSwitch]\",\n            \"type\": \"Microsoft.Resources/deployments\",\n            \"apiVersion\": \"2017-05-10\",\n            \"dependsOn\": [\n                \"[concat('Microsoft.Compute/virtualMachines/',parameters('moodleCommon').ctlrVmName)]\"\n            ],\n            \"name\": \"[concat(parameters('moodleCommon').ctlrVmName,'-Backup')]\",\n            \"properties\": {\n                \"mode\": \"Incremental\",\n                \"parameters\": {\n                    \"moodleCommon\": {\n                        \"value\": \"[parameters('moodleCommon')]\"\n                    },\n                    \"vmName\": {\n                        \"value\": \"[parameters('moodleCommon').ctlrVmName]\"\n                    }\n                },\n                \"templateLink\": {\n                    \"uri\": \"[concat(parameters('moodleCommon').baseTemplateUrl,'recoveryservicesEnlist.json',parameters('moodleCommon').artifactsSasToken)]\"\n                }\n            }\n        }\n    ],\n    \"variables\": {\n        \"documentation01\": \"This sub-template drives the controller/jump-box which is used as the access-point for other moodle VM's \",\n        \"documentation02\": \"It expects certain values in the 'common' datastructure.\",\n        \"documentation03\": \"    vnetName     - name of virtual network\",\n        \"documentation04\": \"    subnetWeb    - name of subnet for controller (and vm scale set)\",\n        \"documentation06\": \"    ctlrPipName  - name of Public IP address for the controller (note that none of the other VM's get a PIP - just the controller\",\n        \"documentation07\": \"    ctlrNicName  - name of the network interface (all VM's must hae a nic) to crate, tied to the public IP address\",\n        \"documentation08\": \"    ctlrNsgName  - name of the network security group, regulating access to/from the controller\",\n        \"documentation09\": \"This sub-template calls other sub-templates\",\n        \"documentation10\": \"    controllerconfig       - conditionally applies post-deployment script on the VM\",\n        \"documentation18\": \"    recoveryservicesEnlist - conditionally enlists the VM into the backup regimen\",\n        \"nicRef\": \"[resourceId('Microsoft.Network/networkInterfaces', parameters('moodleCommon').ctlrNicName)]\",\n        \"nsgRef\": \"[resourceId('Microsoft.Network/networkSecurityGroups', parameters('moodleCommon').ctlrNsgName)]\",\n        \"singleQuote\": \"'\",\n        \"copy\": [\n            {\n                \"name\": \"nfsDiskArray\",\n                \"count\": 8,\n                \"input\": {\n                    \"managedDisk\": {\n                        \"storageAccountType\": \"Premium_LRS\"\n                    },\n                    \"diskSizeGB\": \"[parameters('moodleCommon').fileServerDiskSize]\",\n                    \"lun\": \"[copyIndex('nfsDiskArray')]\",\n                    \"createOption\": \"Empty\"\n                }\n            }\n        ]\n    },\n    \"outputs\": {\n        \"controllerIP\": {\n            \"value\": \"[reference(resourceId('Microsoft.Network/publicIPAddresses', parameters('moodleCommon').ctlrPipName), '2017-10-01').ipAddress]\",\n            \"type\": \"string\"\n        }\n    }\n}\n"
  },
  {
    "path": "nested/controllersetup.json",
    "content": "{\n    \"$schema\": \"http://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#\",\n    \"contentVersion\": \"1.0.0.0\",\n    \"parameters\": {\n        \"moodleCommon\": {\n            \"metadata\": {\n                \"description\": \"Common Moodle values\"\n            },\n            \"type\": \"object\"\n        }\n    },\n    \"resources\": [\n        {\n            \"type\": \"Microsoft.Compute/virtualMachines/extensions\",\n            \"apiVersion\": \"2017-03-30\",\n            \"location\": \"[parameters('moodleCommon').location]\",\n            \"name\": \"[concat(parameters('moodleCommon').ctlrVmName,'/','install_moodle')]\",\n            \"properties\": {\n                \"autoUpgradeMinorVersion\": true,\n                \"publisher\": \"Microsoft.Azure.Extensions\",\n                \"settings\": {\n                    \"fileUris\": [\n                        \"[variables('scriptUri')]\",\n                        \"[parameters('moodleCommon').commonFunctionsScriptUri]\"\n                    ]\n                },\n                \"protectedSettings\":{\n                    \"commandToExecute\": \"[concat('bash ', parameters('moodleCommon').moodleInstallScriptFilename, ' ', parameters('moodleCommon').moodleOnAzureConfigsJsonPath)]\"\n                },\n                \"type\": \"CustomScript\",\n                \"typeHandlerVersion\": \"2.0\"\n            },\n            \"tags\": {\n                \"displayName\": \"install_moodle\"\n            }\n        }\n    ],\n    \"variables\": {\n        \"documentation01\": \"This sub-template applies a specific post-deployment script to the controller vm\",\n        \"documentation02\": \"It expects certain values in the 'common' datastructure.\",\n        \"documentation03\": \"    scriptLocation              - web URI\",\n        \"documentation04\": \"    moodleInstallScriptFilename - name of script file\",\n        \"documentation05\": \"    siteURL                     - URL of the website\",\n        \"documentation06\": \"    gfsNameRoot                 - nameroot of gluster farm - note that the code applies a 0 to get to the first node\",\n        \"documentation07\": \"    ctlrVmName                  - name of the controller/jumpb ox VM\",\n        \"documentation08\": \"    dbServerType                - postgres or mysql\",\n        \"documentation09\": \"    moodleDbName                - database name for moodle\",\n        \"documentation10\": \"    moodleDbUser                - database user for moodle\",\n        \"documentation11\": \"    moodleDbPass                - database password for moodleDbUser\",\n        \"documentation12\": \"    moodleAdminPass             - password for moodle admin user\",\n        \"documentation13\": \"    mssqlDbServiceObjectiveName - MS SQL porformance tier.\",\n        \"documentation14\": \"    mssqlDbEdition              - MS SQL edition tier\",\n        \"documentation15\": \"    mssqlDbSize                 - MS SQL database size\",\n\n        \"scriptUri\": \"[concat(parameters('moodleCommon').scriptLocation,parameters('moodleCommon').moodleInstallScriptFilename,parameters('moodleCommon').artifactsSasToken)]\"\n    }\n}\n"
  },
  {
    "path": "nested/db-mssql.json",
    "content": "{\n    \"$schema\": \"http://schema.management.azure.com/schemas/2015-01-01-preview/deploymentTemplate.json#\",\n    \"contentVersion\": \"1.0.0.0\",\n    \"parameters\": {\n        \"moodleCommon\": {\n            \"metadata\": {\n                \"description\": \"Common Moodle values\"\n            },\n            \"type\": \"object\"\n        },\n        \"lbPubIp\": {\n            \"metadata\": {\n                \"description\": \"Public IP address of the deployed load balancer\"\n            },\n            \"type\": \"string\"\n        },\n        \"lbOut001PubIp\": {\n            \"metadata\": {\n                \"description\": \"Outgoing Public IP address of the deployed load balancer\"\n            },\n            \"type\": \"string\"\n        },\n        \"lbOut002PubIp\": {\n            \"metadata\": {\n                \"description\": \"Outgoing Public IP address of the deployed load balancer\"\n            },\n            \"type\": \"string\"\n        },\n        \"ctlrPubIp\": {\n            \"metadata\": {\n                \"description\": \"Public IP address of the deployed controller VM\"\n            },\n            \"type\": \"string\"\n        },\n        \"subnetIdDb\": {\n            \"metadata\": {\n                \"description\": \"Azure resource ID of the subnet where the Db is to be deployed.(if deployed in vnet)\"\n            },\n            \"type\": \"string\"\n        }\n    },\n    \"resources\": [\n        {\n            \"type\": \"Microsoft.Sql/servers\",\n            \"apiVersion\": \"2015-05-01-preview\",\n            \"kind\": \"\",\n            \"location\": \"[parameters('moodleCommon').location]\",\n            \"name\": \"[parameters('moodleCommon').serverName]\",\n            \"properties\": {\n                \"administratorLogin\": \"[parameters('moodleCommon').dbLogin]\",\n                \"administratorLoginPassword\": \"[parameters('moodleCommon').dbLoginPassword]\",\n                \"version\": \"[parameters('moodleCommon').mssqlVersion]\"\n            },\n            \"resources\": [\n                {\n                    \"apiVersion\": \"2014-04-01\",\n                    \"dependsOn\": [\n                        \"[concat('Microsoft.Sql/servers/', parameters('moodleCommon').serverName)]\"\n                    ],\n                    \"location\": \"[parameters('moodleCommon').location]\",\n                    \"name\": \"mssql-firewall-allow-lb\",\n                    \"properties\": {\n                        \"startIpAddress\": \"[parameters('lbPubIp')]\",\n                        \"endIpAddress\": \"[parameters('lbPubIp')]\"\n                    },\n                    \"type\": \"firewallRules\"\n                },\n                {\n                    \"apiVersion\": \"2014-04-01\",\n                    \"dependsOn\": [\n                        \"[concat('Microsoft.Sql/servers/', parameters('moodleCommon').serverName)]\"\n                    ],\n                    \"location\": \"[parameters('moodleCommon').location]\",\n                    \"name\": \"mssql-firewall-allow-lb-out001\",\n                    \"properties\": {\n                        \"startIpAddress\": \"[parameters('lbOut001PubIp')]\",\n                        \"endIpAddress\": \"[parameters('lbOut001PubIp')]\"\n                    },\n                    \"type\": \"firewallRules\"\n                },\n                {\n                    \"apiVersion\": \"2014-04-01\",\n                    \"dependsOn\": [\n                        \"[concat('Microsoft.Sql/servers/', parameters('moodleCommon').serverName)]\"\n                    ],\n                    \"location\": \"[parameters('moodleCommon').location]\",\n                    \"name\": \"mssql-firewall-allow-lb-out002\",\n                    \"properties\": {\n                        \"startIpAddress\": \"[parameters('lbOut002PubIp')]\",\n                        \"endIpAddress\": \"[parameters('lbOut002PubIp')]\"\n                    },\n                    \"type\": \"firewallRules\"\n                },\n                {\n                    \"apiVersion\": \"2014-04-01\",\n                    \"dependsOn\": [\n                        \"[concat('Microsoft.Sql/servers/', parameters('moodleCommon').serverName)]\"\n                    ],\n                    \"location\": \"[parameters('moodleCommon').location]\",\n                    \"name\": \"mssql-firewall-allow-ctlr\",\n                    \"properties\": {\n                        \"startIpAddress\": \"[parameters('ctlrPubIp')]\",\n                        \"endIpAddress\": \"[parameters('ctlrPubIp')]\"\n                    },\n                    \"type\": \"firewallRules\"\n                }\n            ]\n        }\n    ],\n    \"outputs\": {\n        \"dbFQDN\": {\n            \"type\": \"string\",\n            \"value\": \"[reference(parameters('moodleCommon').serverName).fullyQualifiedDomainName]\"\n        }\n    },\n    \"variables\": {\n        \"documentation1\": \"This sub-template creates a mssql server.  It expects certain values in the 'common' datastructure.\",\n        \"documentation10\": \" serverName                 - Mssql server name\",\n        \"documentation11\": \" mssqlVersion               - Mssql version\",\n        \"documentation2\": \" administratorLogin          - Mssql admin username\",\n        \"documentation3\": \" administratorLoginPassword  - Mssql admin password\",\n        \"documentation4\": \" location                    - Mssql server location\"\n    }\n}\n"
  },
  {
    "path": "nested/db-mysql.json",
    "content": "{\n    \"$schema\": \"http://schema.management.azure.com/schemas/2014-04-01-preview/deploymentTemplate.json#\",\n    \"contentVersion\": \"1.0.0.0\",\n    \"parameters\": {\n        \"moodleCommon\": {\n            \"metadata\": {\n                \"description\": \"Common Moodle values\"\n            },\n            \"type\": \"object\"\n        },\n        \"lbPubIp\": {\n            \"metadata\": {\n                \"description\": \"Public IP address of the deployed load balancer\"\n            },\n            \"type\": \"string\"\n        },\n        \"lbOut001PubIp\": {\n            \"metadata\": {\n                \"description\": \"Outgoing Public IP address of the deployed load balancer\"\n            },\n            \"type\": \"string\"\n        },\n        \"lbOut002PubIp\": {\n            \"metadata\": {\n                \"description\": \"Outgoing Public IP address of the deployed load balancer\"\n            },\n            \"type\": \"string\"\n        },\n        \"ctlrPubIp\": {\n            \"metadata\": {\n                \"description\": \"Public IP address of the deployed controller VM\"\n            },\n            \"type\": \"string\"\n        },\n        \"subnetIdDb\": {\n            \"metadata\": {\n                \"description\": \"Azure resource ID of the subnet where the Db is to be deployed.(if deployed in vnet)\"\n            },\n            \"type\": \"string\"\n        }\n    },\n    \"resources\": [\n        {\n            \"type\": \"Microsoft.DBforMySQL/servers\",\n            \"apiVersion\": \"2017-12-01\",\n            \"kind\": \"\",\n            \"location\": \"[parameters('moodleCommon').location]\",\n            \"name\": \"[parameters('moodleCommon').serverName]\",\n            \"properties\": {\n                \"administratorLogin\": \"[parameters('moodleCommon').dbLogin]\",\n                \"administratorLoginPassword\": \"[parameters('moodleCommon').dbLoginPassword]\",\n                \"sslEnforcement\": \"[parameters('moodleCommon').sslEnforcement]\",\n                \"storageProfile\": {\n                    \"storageMB\": \"[mul(parameters('moodleCommon').mysqlPgresStgSizeGB, 1024)]\",\n                    \"backupRetentionDays\": \"35\",\n                    \"geoRedundantBackup\": \"Enabled\"\n                },\n                \"version\": \"[parameters('moodleCommon').mysqlVersion]\"\n            },\n            \"sku\": {\n                \"capacity\": \"[parameters('moodleCommon').mysqlPgresVcores]\",\n                \"name\": \"[parameters('moodleCommon').mysqlPgresSkuName]\",\n                \"tier\": \"[parameters('moodleCommon').mysqlPgresSkuTier]\",\n                \"family\": \"[parameters('moodleCommon').mysqlPgresSkuHwFamily]\"\n            },\n            \"resources\": [\n                {\n                    \"apiVersion\": \"2017-12-01\",\n                    \"dependsOn\": [\n                        \"[concat('Microsoft.DBforMySQL/servers/', parameters('moodleCommon').serverName)]\"\n                    ],\n                    \"location\": \"[parameters('moodleCommon').location]\",\n                    \"name\": \"mysql-firewall-allow-lb\",\n                    \"properties\": {\n                        \"startIpAddress\": \"[parameters('lbPubIp')]\",\n                        \"endIpAddress\": \"[parameters('lbPubIp')]\"\n                    },\n                    \"type\": \"firewallRules\"\n                },\n                {\n                    \"apiVersion\": \"2017-12-01\",\n                    \"dependsOn\": [\n                        \"[concat('Microsoft.DBforMySQL/servers/', parameters('moodleCommon').serverName)]\"\n                    ],\n                    \"location\": \"[parameters('moodleCommon').location]\",\n                    \"name\": \"mysql-firewall-allow-lb-out001\",\n                    \"properties\": {\n                        \"startIpAddress\": \"[parameters('lbOut001PubIp')]\",\n                        \"endIpAddress\": \"[parameters('lbOut001PubIp')]\"\n                    },\n                    \"type\": \"firewallRules\"\n                },\n                {\n                    \"apiVersion\": \"2017-12-01\",\n                    \"dependsOn\": [\n                        \"[concat('Microsoft.DBforMySQL/servers/', parameters('moodleCommon').serverName)]\"\n                    ],\n                    \"location\": \"[parameters('moodleCommon').location]\",\n                    \"name\": \"mysql-firewall-allow-lb-out002\",\n                    \"properties\": {\n                        \"startIpAddress\": \"[parameters('lbOut002PubIp')]\",\n                        \"endIpAddress\": \"[parameters('lbOut002PubIp')]\"\n                    },\n                    \"type\": \"firewallRules\"\n                },\n                {\n                    \"apiVersion\": \"2017-12-01\",\n                    \"dependsOn\": [\n                        \"[concat('Microsoft.DBforMySQL/servers/', parameters('moodleCommon').serverName)]\"\n                    ],\n                    \"location\": \"[parameters('moodleCommon').location]\",\n                    \"name\": \"mysql-firewall-allow-ctlr\",\n                    \"properties\": {\n                        \"startIpAddress\": \"[parameters('ctlrPubIp')]\",\n                        \"endIpAddress\": \"[parameters('ctlrPubIp')]\"\n                    },\n                    \"type\": \"firewallRules\"\n                }\n            ]\n        }\n    ],\n    \"outputs\": {\n        \"dbFQDN\": {\n            \"type\": \"string\",\n            \"value\": \"[reference(parameters('moodleCommon').serverName).fullyQualifiedDomainName]\"\n        }\n    },\n    \"variables\": {\n        \"documentation1\": \"This sub-template creates a mysql server.  It expects certain values in the 'common' datastructure.\",\n        \"documentation10\": \" serverName                 - Mysql server name\",\n        \"documentation11\": \" mysqlVersion               - Mysql version\",\n        \"documentation2\": \" administratorLogin          - mysql admin username\",\n        \"documentation3\": \" administratorLoginPassword  - mysql admin password\",\n        \"documentation4\": \" location                    - Mysql server location\",\n        \"documentation5\": \" mysqlPgresVcores            - Mysql database trasaction units\",\n        \"documentation7\": \" mysqlPgresSkuName           - Mysql sku name\",\n        \"documentation8\": \" mysqlPgresStgSizeGB         - Mysql sku size in mb\",\n        \"documentation9\": \" mysqlPgresSkuTier           - Mysql sku tier\",\n        \"documentationA\": \" mysqlPgresSkuHwFamily       - Mysql sku hardware family\"\n    }\n}\n"
  },
  {
    "path": "nested/db-mysqlflex.json",
    "content": "{\n    \"$schema\": \"http://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#\",\n    \"contentVersion\": \"1.0.0.0\",\n    \"parameters\": {\n      \"moodleCommon\": {\n          \"metadata\": {\n              \"description\": \"Common Moodle values\"\n          },\n          \"type\": \"object\"\n      },\n      \"lbPubIp\": {\n        \"metadata\": {\n            \"description\": \"Public IP address of the deployed load balancer\"\n        },\n        \"type\": \"string\"\n      },\n      \"lbOut001PubIp\": {\n          \"metadata\": {\n              \"description\": \"Outgoing Public IP address of the deployed load balancer\"\n          },\n          \"type\": \"string\"\n      },\n      \"lbOut002PubIp\": {\n          \"metadata\": {\n              \"description\": \"Outgoing Public IP address of the deployed load balancer\"\n          },\n          \"type\": \"string\"\n      },\n      \"ctlrPubIp\": {\n          \"metadata\": {\n              \"description\": \"Public IP address of the deployed controller VM\"\n          },\n          \"type\": \"string\"\n      },\n      \"subnetIdDb\": {\n          \"metadata\": {\n              \"description\": \"Azure resource ID of the subnet where the Db is to be deployed.(if deployed in vnet)\"\n          },\n          \"type\": \"string\"\n      }\n    },\n    \"resources\": [\n      {\n        \"type\": \"Microsoft.DBforMySQL/flexibleServers\",\n        \"apiVersion\": \"2021-05-01\",\n        \"location\": \"[parameters('moodleCommon').location]\",\n        \"name\": \"[parameters('moodleCommon').serverName]\",\n        \"sku\": {\n          \"name\": \"[parameters('moodleCommon').mysqlflexSkuName]\",\n          \"tier\": \"[parameters('moodleCommon').mysqlflexSkuTier]\"\n        },\n        \"properties\": {\n          \"version\": \"[parameters('moodleCommon').mysqlVersion]\",\n          \"administratorLogin\": \"[parameters('moodleCommon').dbLogin]\",\n          \"administratorLoginPassword\": \"[parameters('moodleCommon').dbLoginPassword]\",\n          \"availabilityZone\": \"[parameters('moodleCommon').mysqlflexAvailabilityZone]\",\n          \"highAvailability\": {\n            \"mode\": \"[parameters('moodleCommon').mysqlflexHaEnabled]\",\n            \"standbyAvailabilityZone\": \"[parameters('moodleCommon').mysqlflexStandbyAvailabilityZone]\"\n          },\n          \"Storage\": {\n            \"storageSizeGB\": \"[parameters('moodleCommon').mysqlflexStgSizeGiB]\",\n            \"iops\": \"[parameters('moodleCommon').mysqlflexStgIops]\",\n            \"autogrow\": \"[parameters('moodleCommon').mysqlflexStgAutogrow]\"\n          },\n          \"network\": \"[if(parameters('moodleCommon').vnetDbDeploySwitch, variables('vnetNetworkData'), createObject())]\",\n          \"Backup\": {\n            \"backupRetentionDays\": 35,\n            \"geoRedundantBackup\": \"Enabled\"\n          }\n        }\n      },\n      {\n        \"type\": \"Microsoft.DBforMySQL/flexibleServers/configurations\",\n        \"apiVersion\": \"2021-05-01\",\n        \"name\": \"[concat(parameters('moodleCommon').serverName, '/require_secure_transport')]\",\n        \"dependsOn\": [\n            \"[concat('Microsoft.DBforMySQL/flexibleServers/', parameters('moodleCommon').serverName)]\"\n        ],\n        \"properties\": {\n          \"value\": \"[parameters('moodleCommon').mysqlflexRequireSecureTransport]\",\n          \"source\": \"user-override\"\n        }\n      },\n      {\n        \"condition\": \"[not(parameters('moodleCommon').vnetDbDeploySwitch)]\",\n        \"type\": \"Microsoft.DBforMySQL/flexibleServers/firewallRules\",\n        \"apiVersion\": \"2021-05-01\",\n        \"name\": \"[concat(parameters('moodleCommon').serverName, '/mysqlflex-firewall-allow-lb')]\",\n        \"dependsOn\": [\n            \"[concat('Microsoft.DBforMySQL/flexibleservers/', parameters('moodleCommon').serverName)]\"\n        ],\n        \"location\": \"[parameters('moodleCommon').location]\",\n        \"properties\": {\n            \"startIpAddress\": \"[parameters('lbPubIp')]\",\n            \"endIpAddress\": \"[parameters('lbPubIp')]\"\n        }\n      },\n      {\n        \"condition\": \"[not(parameters('moodleCommon').vnetDbDeploySwitch)]\",\n        \"type\": \"Microsoft.DBforMySQL/flexibleServers/firewallRules\",\n        \"apiVersion\": \"2021-05-01\",\n        \"name\": \"[concat(parameters('moodleCommon').serverName, '/mysqlflex-firewall-allow-lb-out001')]\",\n        \"dependsOn\": [\n            \"[concat('Microsoft.DBforMySQL/flexibleservers/', parameters('moodleCommon').serverName)]\"\n        ],\n        \"location\": \"[parameters('moodleCommon').location]\",\n        \"properties\": {\n            \"startIpAddress\": \"[parameters('lbOut001PubIp')]\",\n            \"endIpAddress\": \"[parameters('lbOut001PubIp')]\"\n        }\n      },\n      {\n        \"condition\": \"[not(parameters('moodleCommon').vnetDbDeploySwitch)]\",\n        \"type\": \"Microsoft.DBforMySQL/flexibleServers/firewallRules\",\n        \"apiVersion\": \"2021-05-01\",\n        \"name\": \"[concat(parameters('moodleCommon').serverName, '/mysqlflex-firewall-allow-lb-out002')]\",\n        \"dependsOn\": [\n            \"[concat('Microsoft.DBforMySQL/flexibleservers/', parameters('moodleCommon').serverName)]\"\n        ],\n        \"location\": \"[parameters('moodleCommon').location]\",\n        \"properties\": {\n            \"startIpAddress\": \"[parameters('lbOut002PubIp')]\",\n            \"endIpAddress\": \"[parameters('lbOut002PubIp')]\"\n        }\n      },\n      {\n        \"condition\": \"[not(parameters('moodleCommon').vnetDbDeploySwitch)]\",\n        \"type\": \"Microsoft.DBforMySQL/flexibleServers/firewallRules\",\n        \"apiVersion\": \"2021-05-01\",\n        \"name\": \"[concat(parameters('moodleCommon').serverName, '/mysqlflex-firewall-allow-ctlr')]\",\n        \"dependsOn\": [\n            \"[concat('Microsoft.DBforMySQL/flexibleservers/', parameters('moodleCommon').serverName)]\"\n        ],\n        \"location\": \"[parameters('moodleCommon').location]\",\n        \"properties\": {\n            \"startIpAddress\": \"[parameters('ctlrPubIp')]\",\n            \"endIpAddress\": \"[parameters('ctlrPubIp')]\"\n        }\n      }\n    ],\n    \"variables\": {\n      \"vnetNetworkData\": {\n        \"delegatedSubnetResourceId\": \"[parameters('subnetIdDb')]\",\n        \"privateDnsZoneResourceId\": \"[resourceId('Microsoft.Network/privateDnsZones', parameters('moodleCommon').mysqlflexPrivateDnsZoneName)]\"\n      }\n    },\n    \"outputs\": {\n      \"dbFQDN\": {\n          \"type\": \"string\",\n          \"value\": \"[reference(parameters('moodleCommon').serverName).fullyQualifiedDomainName]\"\n      }\n    }\n}"
  },
  {
    "path": "nested/db-postgres.json",
    "content": "{\n    \"$schema\": \"http://schema.management.azure.com/schemas/2014-04-01-preview/deploymentTemplate.json#\",\n    \"contentVersion\": \"1.0.0.0\",\n    \"parameters\": {\n        \"moodleCommon\": {\n            \"metadata\": {\n                \"description\": \"Common Moodle values\"\n            },\n            \"type\": \"object\"\n        },\n        \"lbPubIp\": {\n            \"metadata\": {\n                \"description\": \"Public IP address of the deployed load balancer\"\n            },\n            \"type\": \"string\"\n        },\n        \"lbOut001PubIp\": {\n            \"metadata\": {\n                \"description\": \"Outgoing Public IP address of the deployed load balancer\"\n            },\n            \"type\": \"string\"\n        },\n        \"lbOut002PubIp\": {\n            \"metadata\": {\n                \"description\": \"Outgoing Public IP address of the deployed load balancer\"\n            },\n            \"type\": \"string\"\n        },\n        \"ctlrPubIp\": {\n            \"metadata\": {\n                \"description\": \"Public IP address of the deployed controller VM\"\n            },\n            \"type\": \"string\"\n        },\n        \"subnetIdDb\": {\n            \"metadata\": {\n                \"description\": \"Azure resource ID of the subnet where the Db is to be deployed.(if deployed in vnet)\"\n            },\n            \"type\": \"string\"\n        }\n    },\n    \"resources\": [\n        {\n            \"type\": \"Microsoft.DBforPostgreSQL/servers\",\n            \"apiVersion\": \"2017-12-01\",\n            \"kind\": \"\",\n            \"location\": \"[parameters('moodleCommon').location]\",\n            \"name\": \"[parameters('moodleCommon').serverName]\",\n            \"properties\": {\n                \"administratorLogin\": \"[parameters('moodleCommon').dbLogin]\",\n                \"administratorLoginPassword\": \"[parameters('moodleCommon').dbLoginPassword]\",\n                \"sslEnforcement\": \"[parameters('moodleCommon').sslEnforcement]\",\n                \"storageProfile\": {\n                    \"storageMB\": \"[mul(parameters('moodleCommon').mysqlPgresStgSizeGB, 1024)]\",\n                    \"backupRetentionDays\": \"35\",\n                    \"geoRedundantBackup\": \"Enabled\"\n                },\n                \"version\": \"[parameters('moodleCommon').postgresVersion]\"\n            },\n            \"sku\": {\n                \"capacity\": \"[parameters('moodleCommon').mysqlPgresVcores]\",\n                \"name\": \"[parameters('moodleCommon').mysqlPgresSkuName]\",\n                \"tier\": \"[parameters('moodleCommon').mysqlPgresSkuTier]\",\n                \"family\": \"[parameters('moodleCommon').mysqlPgresSkuHwFamily]\"\n            },\n            \"resources\": [\n                {\n                    \"apiVersion\": \"2017-12-01\",\n                    \"dependsOn\": [\n                        \"[concat('Microsoft.DBforPostgreSQL/servers/', parameters('moodleCommon').serverName)]\"\n                    ],\n                    \"location\": \"[parameters('moodleCommon').location]\",\n                    \"name\": \"postgres-firewall-allow-lb\",\n                    \"properties\": {\n                        \"startIpAddress\": \"[parameters('lbPubIp')]\",\n                        \"endIpAddress\": \"[parameters('lbPubIp')]\"\n                    },\n                    \"type\": \"firewallRules\"\n                },\n                {\n                    \"apiVersion\": \"2017-12-01\",\n                    \"dependsOn\": [\n                        \"[concat('Microsoft.DBforPostgreSQL/servers/', parameters('moodleCommon').serverName)]\"\n                    ],\n                    \"location\": \"[parameters('moodleCommon').location]\",\n                    \"name\": \"postgres-firewall-allow-lb-out001\",\n                    \"properties\": {\n                        \"startIpAddress\": \"[parameters('lbOut001PubIp')]\",\n                        \"endIpAddress\": \"[parameters('lbOut001PubIp')]\"\n                    },\n                    \"type\": \"firewallRules\"\n                },\n                {\n                    \"apiVersion\": \"2017-12-01\",\n                    \"dependsOn\": [\n                        \"[concat('Microsoft.DBforPostgreSQL/servers/', parameters('moodleCommon').serverName)]\"\n                    ],\n                    \"location\": \"[parameters('moodleCommon').location]\",\n                    \"name\": \"postgres-firewall-allow-lb-out002\",\n                    \"properties\": {\n                        \"startIpAddress\": \"[parameters('lbOut002PubIp')]\",\n                        \"endIpAddress\": \"[parameters('lbOut002PubIp')]\"\n                    },\n                    \"type\": \"firewallRules\"\n                },\n                {\n                    \"apiVersion\": \"2017-12-01\",\n                    \"dependsOn\": [\n                        \"[concat('Microsoft.DBforPostgreSQL/servers/', parameters('moodleCommon').serverName)]\"\n                    ],\n                    \"location\": \"[parameters('moodleCommon').location]\",\n                    \"name\": \"postgres-firewall-allow-ctlr\",\n                    \"properties\": {\n                        \"startIpAddress\": \"[parameters('ctlrPubIp')]\",\n                        \"endIpAddress\": \"[parameters('ctlrPubIp')]\"\n                    },\n                    \"type\": \"firewallRules\"\n                }\n            ]\n        }\n    ],\n    \"outputs\": {\n        \"dbFQDN\": {\n            \"type\": \"string\",\n            \"value\": \"[reference(parameters('moodleCommon').serverName).fullyQualifiedDomainName]\"\n        }\n    },\n    \"variables\": {\n        \"documentation1\": \"This sub-template creates a postgresql server.  It expects certain values in the 'common' datastructure.\",\n        \"documentation10\": \" serverName                 - Postgresql server name\",\n        \"documentation11\": \" postgresVersion            - Postgresql version\",\n        \"documentation2\": \" administratorLogin          - postgresql admin username\",\n        \"documentation3\": \" administratorLoginPassword  - postgresql admin password\",\n        \"documentation4\": \" location                    - Postgresql server location\",\n        \"documentation5\": \" mysqlPgresVcores            - Postgresql database trasaction units\",\n        \"documentation7\": \" mysqlPgresSkuName           - Postgresql sku name\",\n        \"documentation8\": \" mysqlPgresStgSizeGB         - Postgresql sku size in mb\",\n        \"documentation9\": \" mysqlPgresSkuTier           - Postgresql sku tier\",\n        \"documentationA\": \" mysqlPgresSkuHwFamily       - Mysql sku hardware family\"\n    }\n}\n"
  },
  {
    "path": "nested/gluster.json",
    "content": "{\n    \"$schema\": \"https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#\",\n    \"contentVersion\": \"1.0.0.0\",\n    \"parameters\": {\n        \"moodleCommon\": {\n            \"metadata\": {\n                \"description\": \"Common Moodle values\"\n            },\n            \"type\": \"object\"\n        },\n        \"subnetIdSan\": {\n            \"metadata\": {\n                \"description\": \"Azure resource ID of the subnet where this gluster cluster is to be deployed\"\n            },\n            \"type\": \"string\"\n        }\n    },\n    \"resources\": [\n        {\n            \"type\": \"Microsoft.Compute/availabilitySets\",\n            \"apiVersion\": \"2017-03-30\",\n            \"location\": \"[parameters('moodleCommon').location]\",\n            \"name\": \"[parameters('moodleCommon').gfxAvailabilitySetName]\",\n            \"properties\": {\n                \"platformFaultDomainCount\": 2,\n                \"platformUpdateDomainCount\": 5\n            },\n            \"sku\": {\n                \"name\": \"Aligned\"\n            },\n            \"tags\": {\n                \"displayName\": \"Gluster Availability Set\"\n            }\n        },\n        {\n            \"type\": \"Microsoft.Resources/deployments\",\n            \"apiVersion\": \"2017-05-10\",\n            \"copy\": {\n                \"count\": \"[parameters('moodleCommon').fileServerVmCount]\",\n                \"name\": \"vmloop\"\n            },\n            \"dependsOn\": [\n                \"[concat('Microsoft.Compute/availabilitySets/',parameters('moodleCommon').gfxAvailabilitySetName)]\"\n            ],\n            \"name\": \"[concat('glustervm',copyindex())]\",\n            \"properties\": {\n                \"mode\": \"Incremental\",\n                \"parameters\": {\n                    \"counter\": {\n                        \"value\": \"[copyindex()]\"\n                    },\n                    \"moodleCommon\": {\n                        \"value\": \"[parameters('moodleCommon')]\"\n                    },\n                    \"subnetIdSan\": {\n                        \"value\": \"[parameters('subnetIdSan')]\"\n                    }\n                },\n                \"templateLink\": {\n                    \"uri\": \"[concat(parameters('moodleCommon').baseTemplateUrl,'glustervm.json',parameters('moodleCommon').artifactsSasToken)]\"\n                }\n            }\n        }\n    ],\n    \"variables\": {\n        \"documentation1\": \"This sub-template drives the gluster (scale-out network-attached storage file system) creation process.\",\n        \"documentation2\": \"It expects certain values in the 'common' datastructure.\",\n        \"documentation4\": \"    gfxAvailabilitySetName  - name of availability set for the gluster farm\",\n        \"documentation5\": \"    fileServerVmCount          - number of nodes to create\",\n        \"documentation6\": \"This sub-template calls other sub-templates\",\n        \"documentation7\": \"    glustervm - number of nodes in the gluster farm\"\n    }\n}\n"
  },
  {
    "path": "nested/glustervm.json",
    "content": "{\n    \"$schema\": \"https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#\",\n    \"contentVersion\": \"1.0.0.0\",\n    \"parameters\": {\n        \"counter\": {\n            \"metadata\": {\n                \"description\": \"from the copyindex function of calling template\"\n            },\n            \"type\": \"int\"\n        },\n        \"moodleCommon\": {\n            \"metadata\": {\n                \"description\": \"Common Moodle values\"\n            },\n            \"type\": \"object\"\n        },\n        \"subnetIdSan\": {\n            \"metadata\": {\n                \"description\": \"Azure resource ID of the subnet where this gluster cluster is to be deployed\"\n            },\n            \"type\": \"string\"\n        }\n    },\n    \"resources\": [\n        {\n            \"type\": \"Microsoft.Network/networkInterfaces\",\n            \"apiVersion\": \"2017-10-01\",\n            \"location\": \"[parameters('moodleCommon').location]\",\n            \"name\": \"[variables('nicName')]\",\n            \"properties\": {\n                \"ipConfigurations\": [\n                    {\n                        \"name\": \"ipcfggfs\",\n                        \"properties\": {\n                            \"privateIPAllocationMethod\": \"Dynamic\",\n                            \"subnet\": {\n                                \"id\": \"[parameters('subnetIdSan')]\"\n                            }\n                        }\n                    }\n                ],\n                \"enableAcceleratedNetworking\": \"[parameters('moodleCommon').enableAccelNwForOtherVmsSwitch]\"\n            },\n            \"tags\": {\n                \"displayName\": \"Gluster VM NIC\"\n            }\n        },\n        {\n            \"type\": \"Microsoft.Compute/virtualMachines\",\n            \"apiVersion\": \"2017-03-30\",\n            \"dependsOn\": [\n                \"[concat('Microsoft.Network/networkInterfaces/', variables('nicName'))]\"\n            ],\n            \"location\": \"[parameters('moodleCommon').location]\",\n            \"name\": \"[variables('vmName')]\",\n            \"properties\": {\n                \"availabilitySet\": {\n                    \"id\": \"[variables('asRef')]\"\n                },\n                \"hardwareProfile\": {\n                    \"vmSize\": \"[parameters('moodleCommon').fileServerVmSku]\"\n                },\n                \"networkProfile\": {\n                    \"networkInterfaces\": [\n                        {\n                            \"id\": \"[variables('nicRef')]\"\n                        }\n                    ]\n                },\n                \"osProfile\": {\n                    \"adminUsername\": \"[parameters('moodleCommon').sshUsername]\",\n                    \"computerName\": \"[variables('vmName')]\",\n                    \"linuxConfiguration\": {\n                        \"disablePasswordAuthentication\": true,\n                        \"ssh\": {\n                            \"publicKeys\": [\n                                {\n                                    \"path\": \"[concat('/home/', parameters('moodleCommon').sshUsername, '/.ssh/authorized_keys')]\",\n                                    \"keyData\": \"[parameters('moodleCommon').sshPublicKey]\"\n                                }\n                            ]\n                        }\n                    }\n                },\n                \"storageProfile\": {\n                    \"imageReference\": \"[parameters('moodleCommon').osType]\",\n                    \"osDisk\": {\n                        \"createOption\": \"FromImage\",\n                        \"managedDisk\": {\n                            \"storageAccountType\": \"[parameters('moodleCommon').osDiskStorageType]\"\n                        },\n                        \"name\": \"[variables('vmName')]\"\n                    },\n                    \"copy\": [\n                        {\n                            \"name\": \"dataDisks\",\n                            \"count\": \"[parameters('moodleCommon').fileServerDiskCount]\",\n                            \"input\": {\n                                \"managedDisk\": {\n                                    \"storageAccountType\": \"Premium_LRS\"\n                                },\n                                \"diskSizeGB\": \"[parameters('moodleCommon').fileServerDiskSize]\",\n                                \"lun\": \"[copyIndex('dataDisks')]\",\n                                \"createOption\": \"Empty\"\n                            }\n                        }\n                    ]\n                }\n            },\n            \"tags\": {\n                \"displayName\": \"Gluster Virtual Machine\"\n            }\n        },\n        {\n            \"condition\": \"[parameters('moodleCommon').applyScriptsSwitch]\",\n            \"type\": \"Microsoft.Resources/deployments\",\n            \"apiVersion\": \"2017-05-10\",\n            \"dependsOn\": [\n                \"[concat('Microsoft.Compute/virtualMachines/',variables('vmName'))]\"\n            ],\n            \"name\": \"[concat(variables('vmName'),'-ScriptProcessor')]\",\n            \"properties\": {\n                \"mode\": \"Incremental\",\n                \"parameters\": {\n                    \"moodleCommon\": {\n                        \"value\": \"[parameters('moodleCommon')]\"\n                    },\n                    \"vmName\": {\n                        \"value\": \"[ variables('vmName')]\"\n                    },\n                    \"vmNumber\": {\n                        \"value\": \"[parameters('counter')]\"\n                    }\n                },\n                \"templateLink\": {\n                    \"uri\": \"[concat(parameters('moodleCommon').baseTemplateUrl,'glustervmsetup.json',parameters('moodleCommon').artifactsSasToken)]\"\n                }\n            }\n        },\n        {\n            \"condition\": \"[parameters('moodleCommon').azureBackupSwitch]\",\n            \"type\": \"Microsoft.Resources/deployments\",\n            \"apiVersion\": \"2017-05-10\",\n            \"dependsOn\": [\n                \"[concat('Microsoft.Compute/virtualMachines/',variables('vmName'))]\"\n            ],\n            \"name\": \"[concat(variables('vmName'),'-Backup')]\",\n            \"properties\": {\n                \"mode\": \"Incremental\",\n                \"parameters\": {\n                    \"moodleCommon\": {\n                        \"value\": \"[parameters('moodleCommon')]\"\n                    },\n                    \"vmName\": {\n                        \"value\": \"[variables('vmName')]\"\n                    }\n                },\n                \"templateLink\": {\n                    \"uri\": \"[concat(parameters('moodleCommon').baseTemplateUrl,'recoveryservicesEnlist.json',parameters('moodleCommon').artifactsSasToken)]\"\n                }\n            }\n        }\n    ],\n    \"variables\": {\n        \"asRef\": \"[resourceId('Microsoft.Compute/availabilitySets', parameters('moodleCommon').gfxAvailabilitySetName)]\",\n        \"documentation01\": \"This sub-template create the nodes of the gluster farm\",\n        \"documentation02\": \"It expects certain values in the 'common' datastructure.\",\n        \"documentation04\": \" gfxAvailabilitySetName  - name of availability set for the gluster farm\",\n        \"documentation05\": \" vnetName                - name of virtual network\",\n        \"documentation06\": \" subnetSan               - name of subnet for gluster\",\n        \"documentation07\": \" gfsNameRoot             - nameroot for the gluster nodes - combined with counter to get actual name of each node - disk and nic follow the naming scheme\",\n        \"documentation08\": \" fileServerVmSku           - VM instance size for gluster nodes\",\n        \"documentation09\": \" sshUsername           - OS accountusername\",\n        \"documentation10\": \" osType                  - an array of value that specifies the type of VM\",\n        \"documentation15\": \"This sub-template calls other sub-templates\",\n        \"documentation17\": \" glustervmconfig         - conditionally applies post-deployment script on the VM\",\n        \"documentation18\": \" recoveryservicesEnlist  - conditionally enlists the VM into the backup regimen\",\n        \"documentation19\": \" fileServerDiskCount        - Number of disks to raid0 for the gluster mount\",\n        \"documentation20\": \" fileServerDiskSize         - Size per disk for gluster\",\n        \"nicName\": \"[concat(variables('vmName'),'-nic')]\",\n        \"nicRef\": \"[resourceId('Microsoft.Network/networkInterfaces', variables('nicName'))]\",\n        \"vmName\": \"[concat(parameters('moodleCommon').gfsNameRoot,parameters('counter'))]\"\n    }\n}\n"
  },
  {
    "path": "nested/glustervmsetup.json",
    "content": "{\n    \"$schema\": \"https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#\",\n    \"contentVersion\": \"1.0.0.0\",\n    \"parameters\": {\n        \"moodleCommon\": {\n            \"metadata\": {\n                \"description\": \"Common Moodle values\"\n            },\n            \"type\": \"object\"\n        },\n        \"vmName\": {\n            \"metadata\": {\n                \"description\": \"Name of VM to process script - not actually used\"\n            },\n            \"type\": \"string\"\n        },\n        \"vmNumber\": {\n            \"metadata\": {\n                \"description\": \"Number of the VM in the pool\"\n            },\n            \"type\": \"int\"\n        }\n    },\n    \"resources\": [\n        {\n            \"type\": \"Microsoft.Compute/virtualMachines/extensions\",\n            \"apiVersion\": \"2017-03-30\",\n            \"location\": \"[parameters('moodleCommon').location]\",\n            \"name\": \"[concat(parameters('vmName'),'/','install_gluster')]\",\n            \"properties\": {\n                \"publisher\": \"Microsoft.Azure.Extensions\",\n                \"settings\": {\n                    \"fileUris\": [\n                        \"[variables('scriptUri')]\"\n                    ]\n                },\n                \"protectedSettings\":{\n                    \"commandToExecute\": \"[variables('cmdExec')]\"\n                },\n                \"type\": \"CustomScript\",\n                \"typeHandlerVersion\": \"2.0\"\n            },\n            \"tags\": {\n                \"displayName\": \"GfsVmExtension\"\n            }\n        }\n    ],\n    \"variables\": {\n        \"cmdExec\": \"[concat('bash ', parameters('moodleCommon').glusterScriptFilename, ' ', parameters('moodleCommon').gfsNameRoot, ' ', parameters('moodleCommon').subnetSanPrefix, ' data ', parameters('vmNumber'), ' ', parameters('moodleCommon').fileServerVmCount)]\",\n        \"documentation01\": \"This sub-template applies a specific post-deployment script to the gluster vms\",\n        \"documentation02\": \"It expects certain values in the 'common' datastructure.\",\n        \"documentation03\": \"    scriptLocation        - partial web URI (equivalent to folder)\",\n        \"documentation04\": \"    glusterScriptFilename - name of script file\",\n        \"documentation06\": \"    gfsNameRoot           - nameroot of gluster farm - note that the code applies a vmNumber to get to the specific node\",\n        \"documentation07\": \"    fileServerVmCount     - number of gluster VMs\",\n        \"scriptUri\": \"[concat(parameters('moodleCommon').scriptLocation,parameters('moodleCommon').glusterScriptFilename,parameters('moodleCommon').artifactsSasToken)]\"\n    }\n}\n"
  },
  {
    "path": "nested/network-subnets.json",
    "content": "{\n    \"$schema\": \"http://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#\",\n    \"contentVersion\": \"1.0.0.0\",\n    \"parameters\": {\n        \"moodleCommon\": {\n            \"metadata\": {\n                \"description\": \"Common Moodle values\"\n            },\n            \"type\": \"object\"\n        },\n        \"vnetName\": {\n            \"metadata\": {\n                \"description\": \"The name of the vnet where subnets should be created\"\n            },\n            \"type\": \"string\"\n        }\n    },\n    \"resources\": [\n        {\n            \"type\": \"Microsoft.Network/virtualNetworks/subnets\",\n            \"apiVersion\": \"2017-10-01\",\n            \"name\": \"[concat(parameters('vnetName'), '/', parameters('moodleCommon').subnetWeb)]\",\n            \"location\": \"[parameters('moodleCommon').location]\",\n            \"properties\": {\n                \"addressPrefix\": \"[parameters('moodleCommon').subnetWebRange]\"\n            }\n        },\n        {\n            \"type\": \"Microsoft.Network/virtualNetworks/subnets\",\n            \"apiVersion\": \"2017-10-01\",\n            \"name\": \"[concat(parameters('vnetName'), '/', parameters('moodleCommon').subnetSan)]\",\n            \"location\": \"[parameters('moodleCommon').location]\",\n            \"dependsOn\": [\n                \"[resourceId('Microsoft.Network/virtualNetworks/subnets', parameters('vnetName'), parameters('moodleCommon').subnetWeb)]\"\n            ],\n            \"properties\": {\n                \"addressPrefix\": \"[parameters('moodleCommon').subnetSanRange]\"\n            }\n        },\n        {\n            \"type\": \"Microsoft.Network/virtualNetworks/subnets\",\n            \"apiVersion\": \"2017-10-01\",\n            \"name\": \"[concat(parameters('vnetName'), '/', parameters('moodleCommon').subnetRedis)]\",\n            \"location\": \"[parameters('moodleCommon').location]\",\n            \"dependsOn\": [\n                \"[resourceId('Microsoft.Network/virtualNetworks/subnets', parameters('vnetName'), parameters('moodleCommon').subnetSan)]\"\n            ],\n            \"properties\": {\n                \"addressPrefix\": \"[parameters('moodleCommon').subnetRedisRange]\"\n            }\n        },\n        {\n            \"type\": \"Microsoft.Network/virtualNetworks/subnets\",\n            \"apiVersion\": \"2017-10-01\",\n            \"name\": \"[concat(parameters('vnetName'), '/', parameters('moodleCommon').subnetElastic)]\",\n            \"location\": \"[parameters('moodleCommon').location]\",\n            \"dependsOn\": [\n                \"[resourceId('Microsoft.Network/virtualNetworks/subnets', parameters('vnetName'), parameters('moodleCommon').subnetRedis)]\"\n            ],\n            \"properties\": {\n                \"addressPrefix\": \"[parameters('moodleCommon').subnetElasticRange]\"\n            }\n        },\n        {\n            \"type\": \"Microsoft.Network/virtualNetworks/subnets\",\n            \"apiVersion\": \"2017-10-01\",\n            \"name\": \"[concat(parameters('vnetName'), '/', parameters('moodleCommon').subnetTika)]\",\n            \"location\": \"[parameters('moodleCommon').location]\",\n            \"dependsOn\": [\n                \"[resourceId('Microsoft.Network/virtualNetworks/subnets', parameters('vnetName'), parameters('moodleCommon').subnetElastic)]\"\n            ],\n            \"properties\": {\n                \"addressPrefix\": \"[parameters('moodleCommon').subnetTikaRange]\"\n            }\n        },\n        {\n            \"condition\": \"[parameters('moodleCommon').vnetGwDeploySwitch]\",\n            \"type\": \"Microsoft.Network/virtualNetworks/subnets\",\n            \"apiVersion\": \"2017-10-01\",\n            \"name\": \"[concat(parameters('vnetName'), '/', parameters('moodleCommon').subnetGateway)]\",\n            \"location\": \"[parameters('moodleCommon').location]\",\n            \"dependsOn\": [\n                \"[resourceId('Microsoft.Network/virtualNetworks/subnets', parameters('vnetName'), parameters('moodleCommon').subnetTika)]\"\n            ],\n            \"properties\": {\n                \"addressPrefix\": \"[parameters('moodleCommon').subnetGatewayRange]\"\n            }\n        },\n        {\n            \"type\": \"Microsoft.Network/virtualNetworks/subnets\",\n            \"apiVersion\": \"2017-10-01\",\n            \"name\": \"[concat(parameters('vnetName'), '/', parameters('moodleCommon').subnetAppGw)]\",\n            \"location\": \"[parameters('moodleCommon').location]\",\n            \"dependsOn\": [\n                \"[resourceId('Microsoft.Network/virtualNetworks/subnets', parameters('vnetName'), parameters('moodleCommon').subnetGateway)]\"\n            ],\n            \"properties\": {\n                \"addressPrefix\": \"[parameters('moodleCommon').subnetAppGwRange]\"\n            }\n        },\n        {\n            \"condition\": \"[and(parameters('moodleCommon').vnetDbDeploySwitch, equals(parameters('moodleCommon').dbServerType, 'mysqlflex'))]\",\n            \"type\": \"Microsoft.Network/virtualNetworks/subnets\",\n            \"apiVersion\": \"2021-05-01\",\n            \"name\": \"[concat(parameters('vnetName'),'/',parameters('moodleCommon').subnetDb)]\",\n            \"location\": \"[parameters('moodleCommon').location]\",\n            \"dependsOn\": [\n                \"[resourceId('Microsoft.Network/virtualNetworks/subnets', parameters('vnetName'), parameters('moodleCommon').subnetAppGw)]\"\n            ],\n            \"properties\": {\n                \"addressPrefix\": \"[parameters('moodleCommon').subnetDbRange]\",\n                \"delegations\": [\n                    {\n                      \"name\": \"MySQLflexibleServers\",\n                      \"properties\": {\n                        \"serviceName\": \"Microsoft.DBforMySQL/flexibleServers\"\n                      }\n                    }\n                ]\n            }\n        }       \n    ],\n    \"variables\": {\n        \"documentation01\": \"This sub-template creates various subnets needed for various components of the cluster.\",\n        \"documentation02\": \"This needs to be done on a separate nested template, in order to allow to create subnets on a customer's BYO-vnet, which may be on a different subscription and/or on a different resource group.\",\n        \"documentation03\": \"The subnets are created one-by-one (using the dependsOn's), to avoid ARM's 'Another operation on this or dependent resource is in progress' failures\"\n    }\n}\n"
  },
  {
    "path": "nested/network-vnet-ddos.json",
    "content": "{\n    \"$schema\": \"http://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#\",\n    \"contentVersion\": \"1.0.0.0\",\n    \"parameters\": {\n        \"moodleCommon\": {\n            \"metadata\": {\n                \"description\": \"Common Moodle values\"\n            },\n            \"type\": \"object\"\n        },\n        \"vnetName\": {\n            \"metadata\": {\n                \"description\": \"The name of the vnet to which the DDoS protection plan shoud be associated\"\n            },\n            \"type\": \"string\"\n        },\n        \"vNetAddressSpace\": {\n            \"metadata\": {\n                \"description\": \"The vNet Address Space to which the DDoS protection plan shoud be associated\"\n            },\n            \"type\": \"string\"\n        }\n    },\n    \"resources\": [\n        {\n            \"type\": \"Microsoft.Network/ddosProtectionPlans\",\n            \"apiVersion\": \"2018-02-01\",\n            \"condition\": \"[parameters('moodleCommon').ddosSwitch]\",\n            \"location\": \"[parameters('moodleCommon').location]\",\n            \"name\": \"[parameters('moodleCommon').ddosPlanName]\"\n        },\n        {\n            \"type\": \"Microsoft.Network/virtualNetworks\",\n            \"apiVersion\": \"2018-02-01\",\n            \"dependsOn\": [\n                \"[resourceId('Microsoft.Network/ddosProtectionPlans', parameters('moodleCommon').ddosPlanName)]\"\n            ],\n            \"location\": \"[parameters('moodleCommon').location]\",\n            \"name\": \"[parameters('vnetName')]\",\n            \"properties\": {\n                \"addressSpace\": {\n                    \"addressPrefixes\": [\n                        \"[parameters('vNetAddressSpace')]\"\n                    ]\n                },\n                \"ddosProtectionPlan\": {\n                    \"id\": \"[resourceId('Microsoft.Network/ddosProtectionPlans', parameters('moodleCommon').ddosPlanName)]\"\n                },\n                \"enableDdosProtection\": \"[parameters('moodleCommon').ddosSwitch]\"\n            }\n        }\n    ],\n    \"variables\": {\n        \"documentation01\": \"This sub-template creates a Azure DDoS protection plan and link it to a vnet\"\n    }\n}\n"
  },
  {
    "path": "nested/network-vnet-privateDnsZone.json",
    "content": "{\n    \"$schema\": \"http://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#\",\n    \"contentVersion\": \"1.0.0.0\",\n    \"parameters\": {\n        \"moodleCommon\": {\n            \"metadata\": {\n                \"description\": \"Common Moodle values\"\n            },\n            \"type\": \"object\"\n        },\n        \"vnetId\": {\n            \"metadata\": {\n                \"description\": \"The resource id of the vnet to which the private DNS Zone shoud be associated\"\n            },\n            \"type\": \"string\"\n        }\n    },\n    \"resources\": [\n        {\n            \"condition\": \"[and(parameters('moodleCommon').vnetDbDeploySwitch, equals(parameters('moodleCommon').dbServerType, 'mysqlflex'))]\",\n            \"type\": \"Microsoft.Network/privateDnsZones\",\n            \"apiVersion\": \"2018-09-01\",\n            \"name\": \"[parameters('moodleCommon').mysqlflexPrivateDnsZoneName]\",\n            \"location\": \"global\",\n            \"tags\": {},\n            \"properties\": {}\n        },\n        {\n            \"condition\": \"[and(parameters('moodleCommon').vnetDbDeploySwitch, equals(parameters('moodleCommon').dbServerType, 'mysqlflex'))]\",\n            \"type\": \"Microsoft.Network/privateDnsZones/virtualNetworkLinks\",\n            \"apiVersion\": \"2020-06-01\",\n            \"dependsOn\": [\n                \"[resourceId('Microsoft.Network/privateDnsZones', parameters('moodleCommon').mysqlflexPrivateDnsZoneName)]\"\n            ],\n            \"name\": \"[concat(parameters('moodleCommon').mysqlflexPrivateDnsZoneName, '/', uniqueString(parameters('vnetId')))]\",\n            \"location\": \"global\",\n            \"properties\": {\n                \"virtualNetwork\": {\n                    \"id\": \"[parameters('vnetId')]\"\n                },\n                \"registrationEnabled\": false\n            }\n        }\n    ],\n    \"variables\": {\n        \"documentation01\": \"This sub-template creates private DNS zones and link it to a vnet\"\n    }\n}\n"
  },
  {
    "path": "nested/network-vnet.json",
    "content": "{\n    \"$schema\": \"http://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#\",\n    \"contentVersion\": \"1.0.0.0\",\n    \"parameters\": {\n        \"moodleCommon\": {\n            \"metadata\": {\n                \"description\": \"Common Moodle values\"\n            },\n            \"type\": \"object\"\n        }\n    },\n    \"resources\": [\n        {\n            \"type\": \"Microsoft.Network/virtualNetworks\",\n            \"apiVersion\": \"2017-10-01\",\n            \"location\": \"[parameters('moodleCommon').location]\",\n            \"name\": \"[parameters('moodleCommon').vnetName]\",\n            \"properties\": {\n                \"addressSpace\": {\n                    \"addressPrefixes\": [\n                        \"[concat(parameters('moodleCommon').vNetAddressSpace,'/16')]\"\n                    ]\n                }\n            }\n        }\n    ],\n    \"variables\": {\n        \"documentation01\": \"This sub-template creates a virtual network when no customer BYO-vnet is specified\"\n    }\n}\n"
  },
  {
    "path": "nested/network.json",
    "content": "{\n    \"$schema\": \"http://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#\",\n    \"contentVersion\": \"1.0.0.0\",\n    \"parameters\": {\n        \"moodleCommon\": {\n            \"metadata\": {\n                \"description\": \"Common Moodle values\"\n            },\n            \"type\": \"object\"\n        }\n    },\n    \"resources\": [\n        {\n            \"condition\": \"[equals(parameters('moodleCommon').customVnetId, '')]\",\n            \"type\": \"Microsoft.Resources/deployments\",\n            \"apiVersion\": \"2017-05-10\",\n            \"name\": \"vnetTemplate\",\n            \"properties\": {\n                \"mode\": \"Incremental\",\n                \"parameters\": {\n                    \"moodleCommon\": {\n                        \"value\": \"[parameters('moodleCommon')]\"\n                    }\n                },\n                \"templateLink\": {\n                    \"uri\": \"[concat(parameters('moodleCommon').baseTemplateUrl,'network-vnet.json',parameters('moodleCommon').artifactsSasToken)]\"\n                }\n            }\n        },\n        {\n            \"apiVersion\": \"2018-02-01\",\n            \"condition\": \"[parameters('moodleCommon').ddosSwitch]\",\n            \"dependsOn\": [\n                \"Microsoft.Resources/deployments/vnetTemplate\"\n            ],\n            \"name\": \"ddosTemplate\",\n            \"subscriptionId\": \"[variables('vnetSub')]\",\n            \"resourceGroup\": \"[variables('vnetRg')]\",\n            \"properties\": {\n                \"mode\": \"Incremental\",\n                \"parameters\": {\n                    \"moodleCommon\": {\n                        \"value\": \"[parameters('moodleCommon')]\"\n                    },\n                    \"vnetName\": {\n                        \"value\": \"[variables('vnetName')]\"\n                    },\n                    \"vNetAddressSpace\": {\n                        \"value\": \"[if(equals(parameters('moodleCommon').customVnetId, ''), concat(parameters('moodleCommon').vNetAddressSpace,'/16'), reference(parameters('moodleCommon').customVnetId, '2017-10-01').addressSpace.addressPrefixes[0])]\"\n                    }\n                },\n                \"templateLink\": {\n                    \"uri\": \"[concat(parameters('moodleCommon').baseTemplateUrl,'network-vnet-ddos.json',parameters('moodleCommon').artifactsSasToken)]\"\n                }\n            },\n            \"type\": \"Microsoft.Resources/deployments\"\n        },\n        {\n            \"type\": \"Microsoft.Resources/deployments\",\n            \"apiVersion\": \"2020-06-01\",\n            \"dependsOn\": [\n                \"Microsoft.Resources/deployments/vnetTemplate\"\n            ],\n            \"name\": \"privateDnsZoneTemplate\",\n            \"subscriptionId\": \"[variables('vnetSub')]\",\n            \"resourceGroup\": \"[variables('vnetRg')]\",\n            \"properties\": {\n                \"mode\": \"Incremental\",\n                \"parameters\": {\n                    \"moodleCommon\": {\n                        \"value\": \"[parameters('moodleCommon')]\"\n                    },\n                    \"vnetId\": {\n                        \"value\": \"[variables('vnetId')]\"\n                    }\n                },\n                \"templateLink\": {\n                    \"uri\": \"[concat(parameters('moodleCommon').baseTemplateUrl,'network-vnet-privateDnsZone.json',parameters('moodleCommon').artifactsSasToken)]\"\n                }\n            }\n        },\n        {\n            \"type\": \"Microsoft.Resources/deployments\",\n            \"apiVersion\": \"2017-05-10\",\n            \"dependsOn\": [\n                \"Microsoft.Resources/deployments/vnetTemplate\",\n                \"Microsoft.Resources/deployments/ddosTemplate\"\n            ],\n            \"name\": \"subnetTemplate\",\n            \"subscriptionId\": \"[variables('vnetSub')]\",\n            \"resourceGroup\": \"[variables('vnetRg')]\",\n            \"properties\": {\n                \"mode\": \"Incremental\",\n                \"parameters\": {\n                    \"moodleCommon\": {\n                        \"value\": \"[parameters('moodleCommon')]\"\n                    },\n                    \"vnetName\": {\n                        \"value\": \"[variables('vnetName')]\"\n                    }\n                },\n                \"templateLink\": {\n                    \"uri\": \"[concat(parameters('moodleCommon').baseTemplateUrl,'network-subnets.json',parameters('moodleCommon').artifactsSasToken)]\"\n                }\n            }\n        },\n        {\n            \"condition\": \"[parameters('moodleCommon').vnetGwDeploySwitch]\",\n            \"type\": \"Microsoft.Network/publicIPAddresses\",\n            \"apiVersion\": \"2019-11-01\",\n            \"location\": \"[parameters('moodleCommon').location]\",\n            \"name\": \"[parameters('moodleCommon').gatewayPublicIPName]\",\n            \"properties\": {\n                \"publicIPAllocationMethod\": \"Dynamic\"\n            },\n            \"tags\": {\n                \"displayName\": \"Virtual network gateway Public IP\"\n            }\n        },\n        {\n            \"condition\": \"[parameters('moodleCommon').vnetGwDeploySwitch]\",\n            \"type\": \"Microsoft.Network/virtualNetworkGateways\",\n            \"apiVersion\": \"2017-10-01\",\n            \"dependsOn\": [\n                \"[resourceId('Microsoft.Network/publicIPAddresses', parameters('moodleCommon').gatewayPublicIPName)]\",\n                \"Microsoft.Resources/deployments/subnetTemplate\"\n            ],\n            \"location\": \"[parameters('moodleCommon').location]\",\n            \"name\": \"[parameters('moodleCommon').gatewayName]\",\n            \"properties\": {\n                \"activeActive\": false,\n                \"enableBgp\": false,\n                \"gatewayType\": \"[parameters('moodleCommon').gatewayType]\",\n                \"ipConfigurations\": [\n                    {\n                        \"name\": \"vnet-Gateway-Config\",\n                        \"properties\": {\n                            \"privateIPAllocationMethod\": \"Dynamic\",\n                            \"publicIPAddress\": {\n                                \"id\": \"[resourceId('Microsoft.Network/publicIPAddresses', parameters('moodleCommon').gatewayPublicIPName)]\"\n                            },\n                            \"subnet\": {\n                                \"id\": \"[variables('subnetIdGateway')]\"\n                            }\n                        }\n                    }\n                ],\n                \"sku\": {\n                    \"name\": \"VpnGw1\",\n                    \"tier\": \"VpnGw1\",\n                    \"capacity\": 2\n                },\n                \"vpnType\": \"[parameters('moodleCommon').vpnType]\"\n            }\n        },\n        {\n            \"condition\": \"[not(equals(parameters('moodleCommon').httpsTermination, 'AppGw'))]\",\n            \"type\": \"Microsoft.Network/publicIPAddresses\",\n            \"sku\": {\n                \"name\": \"[parameters('moodleCommon').lbSku]\",\n                \"tier\": \"[parameters('moodleCommon').lbTier]\"\n            },\n            \"apiVersion\": \"2019-11-01\",\n            \"location\": \"[parameters('moodleCommon').location]\",\n            \"name\": \"[parameters('moodleCommon').lbPipName]\",\n            \"properties\": {\n                \"dnsSettings\": {\n                    \"domainNameLabel\": \"[parameters('moodleCommon').lbName]\"\n                },\n                \"publicIPAllocationMethod\": \"Static\"\n            },\n            \"tags\": {\n                \"displayName\": \"Load Balancer Public IP\"\n            }\n        },\n        {\n            \"condition\": \"[not(equals(parameters('moodleCommon').httpsTermination, 'AppGw'))]\",\n            \"type\": \"Microsoft.Network/publicIPAddresses\",\n            \"sku\": {\n                \"name\": \"[parameters('moodleCommon').lbSku]\",\n                \"tier\": \"[parameters('moodleCommon').lbTier]\"\n            },\n            \"apiVersion\": \"2019-11-01\",\n            \"location\": \"[parameters('moodleCommon').location]\",\n            \"name\": \"[parameters('moodleCommon').lbOutPipName001]\",\n            \"properties\": {\n                \"dnsSettings\": {\n                    \"domainNameLabel\": \"[parameters('moodleCommon').lbOutName001]\"\n                },\n                \"publicIPAllocationMethod\": \"Static\"\n            },\n            \"tags\": {\n                \"displayName\": \"Load Balancer Outbound Public IP 001\"\n            }\n        },\n        {\n            \"condition\": \"[not(equals(parameters('moodleCommon').httpsTermination, 'AppGw'))]\",\n            \"type\": \"Microsoft.Network/publicIPAddresses\",\n            \"sku\": {\n                \"name\": \"[parameters('moodleCommon').lbSku]\",\n                \"tier\": \"[parameters('moodleCommon').lbTier]\"\n            },\n            \"apiVersion\": \"2019-11-01\",\n            \"location\": \"[parameters('moodleCommon').location]\",\n            \"name\": \"[parameters('moodleCommon').lbOutPipName002]\",\n            \"properties\": {\n                \"dnsSettings\": {\n                    \"domainNameLabel\": \"[parameters('moodleCommon').lbOutName002]\"\n                },\n                \"publicIPAllocationMethod\": \"Static\"\n            },\n            \"tags\": {\n                \"displayName\": \"Load Balancer Outbound Public IP 002\"\n            }\n        },\n        {\n            \"condition\": \"[equals(parameters('moodleCommon').httpsTermination, 'AppGw')]\",\n            \"type\": \"Microsoft.Network/publicIPAddresses\",\n            \"apiVersion\": \"2019-11-01\",\n            \"location\": \"[parameters('moodleCommon').location]\",\n            \"name\": \"[parameters('moodleCommon').appGwPipName]\",\n            \"sku\" : {\n                \"name\" : \"[if(endswith(parameters('moodleCommon').appGwSkuName,'v2'),'Standard','Basic')]\"\n            },\n            \"properties\": {                \n                \"dnsSettings\": {\n                    \"domainNameLabel\": \"[parameters('moodleCommon').appGwName]\"\n                },\n                \"publicIPAllocationMethod\": \"[if(endswith(parameters('moodleCommon').appGwSkuName,'v2'),'Static','Dynamic')]\"\n            }\n        },\n        {\n            \"type\": \"Microsoft.Network/publicIPAddresses\",\n            \"apiVersion\": \"2019-11-01\",\n            \"location\": \"[parameters('moodleCommon').location]\",\n            \"name\": \"[parameters('moodleCommon').ctlrPipName]\",\n            \"properties\": {\n                \"dnsSettings\": {\n                    \"domainNameLabel\": \"[parameters('moodleCommon').ctlrPipName]\"\n                },\n                \"publicIPAllocationMethod\": \"Static\"\n            },\n            \"tags\": {\n                \"displayName\": \"Controller VM Public IP\"\n            }\n        },\n        {\n            \"condition\": \"[not(equals(parameters('moodleCommon').httpsTermination, 'AppGw'))]\",\n            \"type\": \"Microsoft.Network/loadBalancers\",\n            \"sku\": {\n                \"name\": \"[parameters('moodleCommon').lbSku]\",\n                \"tier\": \"[parameters('moodleCommon').lbTier]\"\n\n            },\n            \"apiVersion\": \"2019-11-01\",\n            \"dependsOn\": [\n                \"[concat('Microsoft.Network/publicIPAddresses/',parameters('moodleCommon').lbPipName)]\",\n                \"[concat('Microsoft.Network/publicIPAddresses/',parameters('moodleCommon').lbOutPipName001)]\",\n                \"[concat('Microsoft.Network/publicIPAddresses/',parameters('moodleCommon').lbOutPipName002)]\"\n            ],\n            \"location\": \"[parameters('moodleCommon').location]\",\n            \"name\": \"[parameters('moodleCommon').lbName]\",\n            \"properties\": {\n                \"backendAddressPools\": [\n                    {\n                        \"name\": \"[parameters('moodleCommon').extBeName ]\"\n                    }\n                ],\n                \"frontendIPConfigurations\": [\n                    {\n                        \"name\": \"[parameters('moodleCommon').extFeName ]\",\n                        \"properties\": {\n                            \"publicIPAddress\": {\n                                \"id\": \"[variables('lbPipID')]\"\n                            }\n                        }\n\n                    },\n                    {\n                        \"name\": \"[parameters('moodleCommon').extOutName001 ]\",\n                        \"properties\": {\n                            \"publicIPAddress\": {\n                                \"id\": \"[variables('lbOutPip001ID')]\"\n                            }\n                        }\n\n                    },\n                    {\n                        \"name\": \"[parameters('moodleCommon').extOutName002 ]\",\n                        \"properties\": {\n                            \"publicIPAddress\": {\n                                \"id\": \"[variables('lbOutPip002ID')]\"\n                            }\n                        }\n\n                    }\n                ],\n                \"loadBalancingRules\": [\n                    {\n                        \"name\": \"HTTP\",\n                        \"properties\": {\n                            \"backendAddressPool\": {\n                                \"id\": \"[variables('extBeID')]\"\n                            },\n                            \"backendPort\": 80,\n                            \"enableFloatingIP\": false,\n                            \"enableTcpReset\": false,\n                            \"loadDistribution\": \"Default\",\n                            \"disableOutboundSnat\": true,\n                            \"frontendIPConfiguration\": {\n                                \"id\": \"[variables('extFeID')]\"\n                            },\n                            \"frontendPort\": 80,\n                            \"idleTimeoutInMinutes\": 5,\n                            \"probe\": {\n                                \"id\": \"[variables('extProbeHTTPID')]\"\n                            },\n                            \"protocol\": \"Tcp\"\n                        }\n                    },\n                    {\n                        \"name\": \"HTTPS\",\n                        \"properties\": {\n                            \"backendAddressPool\": {\n                                \"id\": \"[variables('extBeID')]\"\n                            },\n                            \"backendPort\": 443,\n                            \"enableFloatingIP\": false,\n                            \"enableTcpReset\": false,\n                            \"loadDistribution\": \"Default\",\n                            \"disableOutboundSnat\": true,\n                            \"frontendIPConfiguration\": {\n                                \"id\": \"[variables('extFeID')]\"\n                            },\n                            \"frontendPort\": 443,\n                            \"idleTimeoutInMinutes\": 5,\n                            \"probe\": {\n                                \"id\": \"[variables('extProbeHTTPSID')]\"\n                            },\n                            \"protocol\": \"Tcp\"\n                        }\n                    }\n                ],\n                \"probes\": [\n                    {\n                        \"name\": \"[parameters('moodleCommon').extProbeHTTP  ]\",\n                        \"properties\": {\n                            \"intervalInSeconds\": 5,\n                            \"numberOfProbes\": 3,\n                            \"port\": 80,\n                            \"protocol\": \"Tcp\"\n                        }\n                    },\n                    {\n                        \"name\": \"[parameters('moodleCommon').extProbeHTTPS  ]\",\n                        \"properties\": {\n                            \"intervalInSeconds\": 5,\n                            \"numberOfProbes\": 3,\n                            \"port\": 443,\n                            \"protocol\": \"Tcp\"\n                        }\n                    }\n                ],\n                \"inboundNatRules\": [],\n                \"outboundRules\": [\n                    {\n                        \"name\": \"Outboundrule001\",\n                        \"properties\": {\n                            \"allocatedOutboundPorts\": 12800,\n                            \"protocol\": \"Tcp\",\n                            \"enableTcpReset\": false,\n                            \"idleTimeoutInMinutes\": 5,\n                            \"backendAddressPool\": {\n                                \"id\": \"[variables('extBeID')]\"\n                            },\n                            \"frontendIPConfigurations\": [\n                                {\n                                    \"id\": \"[variables('extOutID001')]\"\n                                },\n                                {\n                                    \"id\": \"[variables('extOutID002')]\"\n                                }\n                            ]\n                        }\n                    }\n                ],\n                \"inboundNatPools\": []\n            }\n        },\n        {\n            \"condition\": \"[equals(parameters('moodleCommon').httpsTermination, 'AppGw')]\",\n            \"type\": \"Microsoft.Resources/deployments\",\n            \"apiVersion\": \"2017-05-10\",\n            \"dependsOn\": [\n                \"Microsoft.Resources/deployments/subnetTemplate\",\n                \"[concat('Microsoft.Network/publicIPAddresses/',parameters('moodleCommon').appGwPipName)]\"\n            ],\n            \"name\": \"appGwTemplate\",\n            \"properties\": {\n                \"mode\": \"Incremental\",\n                \"parameters\": {\n                    \"moodleCommon\": {\n                        \"value\": \"[parameters('moodleCommon')]\"\n                    },\n                    \"subnetIdAppGw\": {\n                        \"value\": \"[variables('subnetIdAppGw')]\"\n                    },\n                    \"sslCertData\": {\n                        \"reference\": {\n                            \"keyVault\": {\n                                \"id\": \"[parameters('moodleCommon').appGwSslCertKeyVaultResourceId]\"\n                            },\n                            \"secretName\": \"[parameters('moodleCommon').appGwSslCertKeyVaultSecretName]\"\n                        }\n                    }\n                },\n                \"templateLink\": {\n                    \"uri\": \"[concat(parameters('moodleCommon').baseTemplateUrl,'appgw.json',parameters('moodleCommon').artifactsSasToken)]\"\n                }\n            }\n        }\n    ],\n    \"variables\": {\n        \"documentation01\": \"This sub-template creates a virtual network with a number of subnets and then creates the moodle load-balancer (or an Azure Application Gateway) with public IP/dns\",\n        \"extBeID\": \"[concat(variables('extLbID'),'/backendAddressPools/',parameters('moodleCommon').extBeName)]\",\n        \"extFeID\": \"[concat(variables('extLbID'),'/frontendIPConfigurations/',parameters('moodleCommon').extFeName)]\",\n        \"extOutID001\": \"[concat(variables('extLbID'),'/frontendIPConfigurations/',parameters('moodleCommon').extOutName001)]\",\n        \"extOutID002\": \"[concat(variables('extLbID'),'/frontendIPConfigurations/',parameters('moodleCommon').extOutName002)]\",\n        \"extLbID\": \"[resourceId('Microsoft.Network/loadBalancers',parameters('moodleCommon').lbName)]\",\n        \"extProbeHTTPID\": \"[concat(variables('extLbID'),'/probes/',parameters('moodleCommon').extProbeHTTP)]\",\n        \"extProbeHTTPSID\": \"[concat(variables('extLbID'),'/probes/',parameters('moodleCommon').extProbeHTTPS)]\",\n        \"lbPipID\": \"[resourceId('Microsoft.Network/publicIPAddresses', parameters('moodleCommon').lbPipName)]\",\n        \"lbOutPip001ID\": \"[resourceId('Microsoft.Network/publicIPAddresses', parameters('moodleCommon').lbOutPipName001)]\",\n        \"lbOutPip002ID\": \"[resourceId('Microsoft.Network/publicIPAddresses', parameters('moodleCommon').lbOutPipName002)]\",\n        \"ctlrPipID\": \"[resourceId('Microsoft.Network/publicIPAddresses', parameters('moodleCommon').ctlrPipName)]\",\n        \"customVnetIdArr\":              \"[split(parameters('moodleCommon').customVnetId, '/')]\",\n        \"vnetSub\":                      \"[if(equals(parameters('moodleCommon').customVnetId, ''), subscription().subscriptionId, variables('customVnetIdArr')[2])]\",\n        \"vnetRg\":                       \"[if(equals(parameters('moodleCommon').customVnetId, ''), resourceGroup().name, variables('customVnetIdArr')[4])]\",\n        \"vnetName\":                     \"[if(equals(parameters('moodleCommon').customVnetId, ''), parameters('moodleCommon').vnetName, variables('customVnetIdArr')[8])]\",\n        \"vnetId\":                       \"[if(equals(parameters('moodleCommon').customVnetId, ''), resourceId('Microsoft.Network/virtualNetworks', parameters('moodleCommon').vnetName), parameters('moodleCommon').customVnetId)]\",\n        \"customVnetSubnetIdWeb\":        \"[concat(parameters('moodleCommon').customVnetId, '/subnets/', parameters('moodleCommon').subnetWeb)]\",\n        \"customVnetSubnetIdSan\":        \"[concat(parameters('moodleCommon').customVnetId, '/subnets/', parameters('moodleCommon').subnetSan)]\",\n        \"customVnetSubnetIdRedis\":      \"[concat(parameters('moodleCommon').customVnetId, '/subnets/', parameters('moodleCommon').subnetRedis)]\",\n        \"customVnetSubnetIdElastic\":    \"[concat(parameters('moodleCommon').customVnetId, '/subnets/', parameters('moodleCommon').subnetElastic)]\",\n        \"customVnetSubnetIdTika\":       \"[concat(parameters('moodleCommon').customVnetId, '/subnets/', parameters('moodleCommon').subnetTika)]\",\n        \"customVnetSubnetIdGateway\":    \"[concat(parameters('moodleCommon').customVnetId, '/subnets/', parameters('moodleCommon').subnetGateway)]\",\n        \"customVnetSubnetIdAppGw\":      \"[concat(parameters('moodleCommon').customVnetId, '/subnets/', parameters('moodleCommon').subnetAppGw)]\",\n        \"customVnetSubnetIdDb\":         \"[concat(parameters('moodleCommon').customVnetId, '/subnets/', parameters('moodleCommon').subnetDb)]\",\n        \"subnetIdWeb\":      \"[if(equals(parameters('moodleCommon').customVnetId, ''), resourceId('Microsoft.Network/virtualNetworks/subnets', variables('vnetName'), parameters('moodleCommon').subnetWeb), variables('customVnetSubnetIdWeb'))]\",\n        \"subnetIdSan\":      \"[if(equals(parameters('moodleCommon').customVnetId, ''), resourceId('Microsoft.Network/virtualNetworks/subnets', variables('vnetName'), parameters('moodleCommon').subnetSan), variables('customVnetSubnetIdSan'))]\",\n        \"subnetIdRedis\":    \"[if(equals(parameters('moodleCommon').customVnetId, ''), resourceId('Microsoft.Network/virtualNetworks/subnets', variables('vnetName'), parameters('moodleCommon').subnetRedis), variables('customVnetSubnetIdRedis'))]\",\n        \"subnetIdElastic\":  \"[if(equals(parameters('moodleCommon').customVnetId, ''), resourceId('Microsoft.Network/virtualNetworks/subnets', variables('vnetName'), parameters('moodleCommon').subnetElastic), variables('customVnetSubnetIdElastic'))]\",\n        \"subnetIdTika\":     \"[if(equals(parameters('moodleCommon').customVnetId, ''), resourceId('Microsoft.Network/virtualNetworks/subnets', variables('vnetName'), parameters('moodleCommon').subnetTika), variables('customVnetSubnetIdTika'))]\",\n        \"subnetIdGateway\":  \"[if(equals(parameters('moodleCommon').customVnetId, ''), resourceId('Microsoft.Network/virtualNetworks/subnets', variables('vnetName'), parameters('moodleCommon').subnetGateway), variables('customVnetSubnetIdGateway'))]\",\n        \"subnetIdAppGw\":    \"[if(equals(parameters('moodleCommon').customVnetId, ''), resourceId('Microsoft.Network/virtualNetworks/subnets', variables('vnetName'), parameters('moodleCommon').subnetAppGw), variables('customVnetSubnetIdAppGw'))]\",\n        \"subnetIdDb\":       \"[if(parameters('moodleCommon').vnetDbDeploySwitch, if(equals(parameters('moodleCommon').customVnetId, ''), resourceId('Microsoft.Network/virtualNetworks/subnets', variables('vnetName'), parameters('moodleCommon').subnetDb), variables('customVnetSubnetIdDb')), '')]\"\n    },\n    \"outputs\": {\n        \"lbPubIp\": {\n            \"value\": \"[if(equals(parameters('moodleCommon').httpsTermination, 'AppGw'), '0.0.0.0', reference(parameters('moodleCommon').lbPipName, '2017-10-01').ipAddress)]\",\n            \"type\": \"string\"\n        },\n        \"lbOut001PubIp\": {\n            \"value\": \"[if(equals(parameters('moodleCommon').httpsTermination, 'AppGw'), '0.0.0.0', reference(parameters('moodleCommon').lbOutPipName001, '2017-10-01').ipAddress)]\",\n            \"type\": \"string\"\n        },\n        \"lbOut002PubIp\": {\n            \"value\": \"[if(equals(parameters('moodleCommon').httpsTermination, 'AppGw'), '0.0.0.0', reference(parameters('moodleCommon').lbOutPipName002, '2017-10-01').ipAddress)]\",\n            \"type\": \"string\"\n        },\n        \"ctlrPubIp\": {\n            \"value\": \"[reference(parameters('moodleCommon').ctlrPipName, '2017-10-01').ipAddress]\",\n            \"type\": \"string\"\n        },\n        \"ctlrPubIpId\": {\n            \"value\": \"[variables('ctlrPipID')]\",\n            \"type\": \"string\"\n        },\n        \"subnetIdWeb\": {\n            \"value\": \"[variables('subnetIdWeb')]\",\n            \"type\": \"string\"\n        },\n        \"subnetIdSan\": {\n            \"value\": \"[variables('subnetIdSan')]\",\n            \"type\": \"string\"\n        },\n        \"subnetIdRedis\": {\n            \"value\": \"[variables('subnetIdRedis')]\",\n            \"type\": \"string\"\n        },\n        \"subnetIdElastic\": {\n            \"value\": \"[variables('subnetIdElastic')]\",\n            \"type\": \"string\"\n        },\n        \"subnetIdTika\": {\n            \"value\": \"[variables('subnetIdTika')]\",\n            \"type\": \"string\"\n        },\n        \"subnetIdDb\": {\n            \"value\": \"[variables('subnetIdDb')]\",\n            \"type\": \"string\"\n        }\n    }\n}\n"
  },
  {
    "path": "nested/nfs-ha-vm.json",
    "content": "{\n    \"$schema\": \"https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#\",\n    \"contentVersion\": \"1.0.0.0\",\n    \"parameters\": {\n        \"_artifactsLocation\": {\n            \"type\": \"string\",\n            \"metadata\": {\n                \"description\": \"The base URI where artifacts required by this template are located. When the template is deployed using the accompanying scripts, a private location in the subscription will be used and this value will be automatically generated.\"\n            }\n        },\n        \"_artifactsLocationSasToken\": {\n            \"type\": \"securestring\",\n            \"metadata\": {\n                \"description\": \"The sasToken required to access _artifactsLocation.  When the template is deployed using the accompanying scripts, a sasToken will be automatically generated.\"\n            },\n            \"defaultValue\": \"\"\n        },\n        \"vmIndex\": {\n            \"metadata\": {\n                \"description\": \"Index of the VM\"\n            },\n            \"type\": \"int\"\n        },\n        \"location\": {\n            \"type\": \"string\",\n            \"defaultValue\": \"[resourceGroup().location]\",\n            \"metadata\": {\n                \"description\": \"Azure location where this template is to be deployed\"\n            }\n        },\n        \"subnetId\": {\n            \"metadata\": {\n                \"description\": \"Azure resource ID of the subnet where this NFS-HA cluster is to be deployed\"\n            },\n            \"type\": \"string\"\n        },\n        \"ipAddrs\": {\n            \"metadata\": {\n                \"description\": \"Statically assigned private IP addresses that should be assigned to the two VMs' NICs to be deployed. Must belong to the IP range of the specified subnet\"\n            },\n            \"type\": \"array\"\n        },\n        \"nfsClientsIPRange\": {\n            \"metadata\": {\n                \"description\": \"IP range of the allowed NFS clients. E.g., 10.0.0.0/24\"\n            },\n            \"type\": \"string\"\n        },\n        \"enableAccelNwSwitch\": {\n            \"metadata\": {\n                \"description\": \"Switch to enable Azure Accelerated Networking (Note: this feature is NOT available for D1-level VM SKU)\"\n            },\n            \"type\": \"bool\",\n            \"defaultValue\": false\n        },\n        \"availSetId\": {\n            \"metadata\": {\n                \"description\": \"Azure resource ID of the availability set where this VM is to be deployed\"\n            },\n            \"type\": \"string\"\n        },\n        \"vmSku\": {\n            \"metadata\": {\n                \"description\": \"Azure VM SKU for the NFS HA VMs\"\n            },\n            \"type\": \"string\",\n            \"defaultValue\": \"Standard_DS2_v2\"\n        },\n        \"adminUserName\": {\n            \"metadata\": {\n                \"description\": \"VM admin user name\"\n            },\n            \"type\": \"string\",\n            \"defaultValue\": \"azureadmin\"\n        },\n        \"sshPublicKey\": {\n            \"metadata\": {\n                \"description\": \"SSH public key for the admin user\"\n            },\n            \"type\": \"string\"\n        },\n        \"osType\": {\n            \"metadata\": {\n                \"description\": \"OS type (offer/publisher/sku/version) info\"\n            },\n            \"type\": \"object\"\n        },\n        \"osDiskStorageType\": {\n            \"defaultValue\": \"Premium_LRS\",\n            \"allowedValues\": [\n                \"Premium_LRS\",\n                \"Standard_LRS\"\n            ],\n            \"metadata\": {\n                \"description\": \"Azure storage type for all VMs' OS disks. With htmlLocalCopySwith true, Premium_LRS (SSD) is strongly recommended, as PHP files will be served from OS disks.\"\n            },\n            \"type\": \"string\"\n        },\n        \"dataDiskCountPerVM\": {\n            \"metadata\": {\n                \"description\": \"Number of data disks per VM. 2 or more disks will be configured as RAID0\"\n            },\n            \"defaultValue\": 1,\n            \"minValue\": 1,\n            \"maxValue\": 8,\n            \"type\": \"int\"\n        },\n        \"dataDiskSizeInGB\": {\n            \"defaultValue\": 32,\n            \"metadata\": {\n                \"description\": \"Size of each disk in an NFS server\"\n            },\n            \"type\": \"int\"\n        },\n        \"resourcesUniqueString\": {\n            \"metadata\": {\n                \"description\": \"Unique string of fixed length (e.g., 6) identifying related resources\"\n            },\n            \"type\": \"string\",\n            \"defaultValue\": \"[substring(uniqueString(resourceGroup().id, deployment().name), 3, 6)]\"\n        },\n        \"lbBeId\": {\n            \"metadata\": {\n                \"description\": \"Azure resource ID of the load balancer backend pool to which this VM's NIC should be added\"\n            },\n            \"type\": \"string\"\n        }\n    },\n    \"resources\": [\n        {\n            \"type\": \"Microsoft.Network/networkInterfaces\",\n            \"apiVersion\": \"2017-10-01\",\n            \"location\": \"[parameters('location')]\",\n            \"name\": \"[variables('nicName')]\",\n            \"properties\": {\n                \"ipConfigurations\": [\n                    {\n                        \"name\": \"[variables('ipcfgName')]\",\n                        \"properties\": {\n                            \"privateIPAllocationMethod\": \"Static\",\n                            \"privateIPAddress\": \"[parameters('ipAddrs')[parameters('vmIndex')]]\",\n                            \"subnet\": {\n                                \"id\": \"[parameters('subnetId')]\"\n                            },\n                            \"loadBalancerBackendAddressPools\": [\n                                {\n                                    \"id\": \"[parameters('lbBeId')]\"\n                                }\n                            ]\n                        }\n                    }\n                ],\n                \"enableAcceleratedNetworking\": \"[parameters('enableAccelNwSwitch')]\"\n            },\n            \"tags\": {\n                \"displayName\": \"[concat('NIC for NFS-HA node', parameters('vmIndex'), ' VM')]\"\n            }\n        },\n        {\n            \"type\": \"Microsoft.Compute/virtualMachines\",\n            \"apiVersion\": \"2017-03-30\",\n            \"dependsOn\": [\n                \"[concat('Microsoft.Network/networkInterfaces/', variables('nicName'))]\"\n            ],\n            \"location\": \"[parameters('location')]\",\n            \"name\": \"[variables('vmResourceName')]\",\n            \"properties\": {\n                \"availabilitySet\": {\n                    \"id\": \"[parameters('availSetId')]\"\n                },\n                \"hardwareProfile\": {\n                    \"vmSize\": \"[parameters('vmSku')]\"\n                },\n                \"networkProfile\": {\n                    \"networkInterfaces\": [\n                        {\n                            \"id\": \"[resourceId('Microsoft.Network/networkInterfaces', variables('nicName'))]\"\n                        }\n                    ]\n                },\n                \"osProfile\": {\n                    \"adminUsername\": \"[parameters('adminUserName')]\",\n                    \"computerName\": \"[variables('vmName')]\",\n                    \"linuxConfiguration\": {\n                        \"disablePasswordAuthentication\": true,\n                        \"ssh\": {\n                            \"publicKeys\": [\n                                {\n                                    \"path\": \"[concat('/home/', parameters('adminUserName'), '/.ssh/authorized_keys')]\",\n                                    \"keyData\": \"[parameters('sshPublicKey')]\"\n                                }\n                            ]\n                        }\n                    }\n                },\n                \"storageProfile\": {\n                    \"imageReference\": \"[parameters('osType')]\",\n                    \"osDisk\": {\n                        \"createOption\": \"FromImage\",\n                        \"managedDisk\": {\n                            \"storageAccountType\": \"[parameters('osDiskStorageType')]\"\n                        },\n                        \"name\": \"[concat(variables('vmResourceName'), '_osDisk')]\"\n                    },\n                    \"copy\": [\n                        {\n                            \"name\": \"dataDisks\",\n                            \"count\": \"[parameters('dataDiskCountPerVM')]\",\n                            \"input\": {\n                                \"managedDisk\": {\n                                    \"storageAccountType\": \"Premium_LRS\"\n                                },\n                                \"diskSizeGB\": \"[parameters('dataDiskSizeInGB')]\",\n                                \"lun\": \"[copyIndex('dataDisks')]\",\n                                \"createOption\": \"Empty\"\n                            }\n                        }\n                    ]\n                }\n            },\n            \"resources\": [\n                {\n                    \"type\": \"extensions\",\n                    \"apiVersion\": \"2017-03-30\",\n                    \"dependsOn\": [\n                        \"[concat('Microsoft.Compute/virtualMachines/', variables('vmResourceName'))]\"\n                    ],\n                    \"location\": \"[parameters('location')]\",\n                    \"name\": \"setup_nfs_ha\",\n                    \"properties\": {\n                        \"publisher\": \"Microsoft.Azure.Extensions\",\n                        \"settings\": {\n                            \"fileUris\": [\n                                \"[variables('scriptUri')]\",\n                                \"[variables('commonFunctionsScriptUri')]\"\n                            ]\n                        },\n                        \"protectedSettings\":{\n                            \"commandToExecute\": \"[variables('cmdExec')]\"\n                        },\n                        \"type\": \"CustomScript\",\n                        \"typeHandlerVersion\": \"2.0\"\n                    },\n                    \"tags\": {\n                        \"displayName\": \"NFS-HA VM setup CustomScript extension\"\n                    }\n                }\n                    ],\n            \"tags\": {\n                \"displayName\": \"[concat('NFS-HA Virtual Machine ', variables('vmName'))]\"\n            }\n        }\n    ],\n    \"variables\": {\n        \"nicName\": \"[concat('nfs-ha-nic', parameters('vmIndex'), '-', parameters('resourcesUniqueString'))]\",\n        \"ipCfgName\": \"[concat('nfs-ha-ipcfg', parameters('vmIndex'))]\",\n        \"vmResourceName\": \"[concat('nfs-ha-vm', parameters('vmIndex'), '-', parameters('resourcesUniqueString'))]\",\n        \"vmName\": \"[concat('hanode', parameters('vmIndex'), '-', parameters('resourcesUniqueString'))]\",\n        \"scriptUri\": \"[concat(parameters('_artifactsLocation'), 'scripts/setup_nfs_ha.sh', parameters('_artifactsLocationSasToken'))]\",\n        \"commonFunctionsScriptUri\": \"[concat(parameters('_artifactsLocation'), 'scripts/helper_functions.sh', parameters('_artifactsLocationSasToken'))]\",\n        \"cmdExec\": \"[concat('bash -x setup_nfs_ha.sh hanode0-', parameters('resourcesUniqueString'), ' ', parameters('ipAddrs')[0], ' hanode1-', parameters('resourcesUniqueString'), ' ', parameters('ipAddrs')[1], ' ', parameters('nfsClientsIPRange'))]\"\n    }\n}\n"
  },
  {
    "path": "nested/nfs-ha.json",
    "content": "{\n    \"$schema\": \"https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#\",\n    \"contentVersion\": \"1.0.0.0\",\n    \"parameters\": {\n        \"_artifactsLocation\": {\n            \"type\": \"string\",\n            \"metadata\": {\n                \"description\": \"The base URI where artifacts required by this template are located. When the template is deployed using the accompanying scripts, a private location in the subscription will be used and this value will be automatically generated.\"\n            }\n        },\n        \"_artifactsLocationSasToken\": {\n            \"type\": \"securestring\",\n            \"metadata\": {\n                \"description\": \"The sasToken required to access _artifactsLocation.  When the template is deployed using the accompanying scripts, a sasToken will be automatically generated.\"\n            },\n            \"defaultValue\": \"\"\n        },\n        \"location\": {\n            \"type\": \"string\",\n            \"defaultValue\": \"[resourceGroup().location]\",\n            \"metadata\": {\n                \"description\": \"Azure location where this template is to be deployed\"\n            }\n        },\n        \"subnetId\": {\n            \"metadata\": {\n                \"description\": \"Azure resource ID of the subnet where this NFS-HA cluster is to be deployed\"\n            },\n            \"type\": \"string\"\n        },\n        \"node0IPAddr\": {\n            \"metadata\": {\n                \"description\": \"IP address of node 0 (statically assigned). E.g., 10.0.0.11. Must belong to the IP range of the specified subnet\"\n            },\n            \"type\": \"string\"\n        },\n        \"node1IPAddr\": {\n            \"metadata\": {\n                \"description\": \"IP address of node 1 (statically assigned). E.g., 10.0.0.22. Must belong to the IP range of the specified subnet\"\n            },\n            \"type\": \"string\"\n        },\n        \"nfsClientsIPRange\": {\n            \"metadata\": {\n                \"description\": \"IP range of the allowed NFS clients. E.g., 10.0.0.0/24\"\n            },\n            \"type\": \"string\"\n        },\n        \"lbFrontEndIpAddr\": {\n            \"metadata\": {\n                \"description\": \"IP address of the load balancer front-end (statically assigned). E.g., 10.0.0.100. Must belong to the IP range of the specified subnet\"\n            },\n            \"type\": \"string\"\n        },\n        \"enableAccelNwSwitch\": {\n            \"metadata\": {\n                \"description\": \"Switch to enable Azure Accelerated Networking (Note: this feature is NOT available for D1-level VM SKU)\"\n            },\n            \"type\": \"bool\",\n            \"defaultValue\": false\n        },\n        \"vmSku\": {\n            \"metadata\": {\n                \"description\": \"Azure VM SKU for the NFS HA VMs\"\n            },\n            \"type\": \"string\",\n            \"defaultValue\": \"Standard_DS2_v2\"\n        },\n        \"adminUserName\": {\n            \"metadata\": {\n                \"description\": \"VM admin user name\"\n            },\n            \"type\": \"string\",\n            \"defaultValue\": \"azureadmin\"\n        },\n        \"sshPublicKey\": {\n            \"metadata\": {\n                \"description\": \"SSH public key for the admin user\"\n            },\n            \"type\": \"string\"\n        },\n        \"osType\": {\n            \"metadata\": {\n                \"description\": \"OS type (offer/publisher/sku/version) info\"\n            },\n            \"type\": \"object\",\n            \"defaultValue\": {\n                \"offer\": \"UbuntuServer\",\n                \"publisher\": \"Canonical\",\n                \"sku\": \"16.04-LTS\",\n                \"version\": \"latest\"\n            }\n        },\n        \"osDiskStorageType\": {\n            \"defaultValue\": \"Premium_LRS\",\n            \"allowedValues\": [\n                \"Premium_LRS\",\n                \"Standard_LRS\"\n            ],\n            \"metadata\": {\n                \"description\": \"Azure storage type for all VMs' OS disks. With htmlLocalCopySwith true, Premium_LRS (SSD) is strongly recommended, as PHP files will be served from OS disks.\"\n            },\n            \"type\": \"string\"\n        },\n        \"dataDiskCountPerVM\": {\n            \"metadata\": {\n                \"description\": \"Number of data disks per VM. 2 or more disks will be configured as RAID0\"\n            },\n            \"defaultValue\": 1,\n            \"minValue\": 1,\n            \"maxValue\": 8,\n            \"type\": \"int\"\n        },\n        \"dataDiskSizeInGB\": {\n            \"defaultValue\": 32,\n            \"metadata\": {\n                \"description\": \"Size per disk in an NFS server\"\n            },\n            \"type\": \"int\"\n        },\n        \"resourcesUniqueString\": {\n            \"metadata\": {\n                \"description\": \"Unique string of fixed length (e.g., 6) identifying related resources\"\n            },\n            \"type\": \"string\",\n            \"defaultValue\": \"[substring(uniqueString(resourceGroup().id, deployment().name), 3, 6)]\"\n        }\n    },\n    \"resources\": [\n        {\n            \"type\": \"Microsoft.Compute/availabilitySets\",\n            \"apiVersion\": \"2017-03-30\",\n            \"location\": \"[parameters('location')]\",\n            \"name\": \"[variables('availSetName')]\",\n            \"properties\": {\n                \"platformFaultDomainCount\": 2,\n                \"platformUpdateDomainCount\": 5\n            },\n            \"sku\": {\n                \"name\": \"Aligned\"\n            },\n            \"tags\": {\n                \"displayName\": \"NFS-HA Availability Set\"\n            }\n        },\n        {\n            \"type\": \"Microsoft.Network/loadBalancers\",\n            \"sku\": {\n                \"name\": \"Basic\"\n            },\n            \"apiVersion\": \"2017-10-01\",\n            \"location\": \"[parameters('location')]\",\n            \"name\": \"[variables('nfsHaLbName')]\",\n            \"properties\": {\n                \"frontendIPConfigurations\": [\n                    {\n                        \"name\": \"[variables('nfsHaLbFeName')]\",\n                        \"properties\": {\n                            \"privateIPAddress\": \"[parameters('lbFrontEndIpAddr')]\",\n                            \"privateIPAllocationMethod\": \"Static\",\n                            \"subnet\": {\n                                \"id\": \"[parameters('subnetId')]\"\n                            }\n                        }\n                    }\n                ],\n                \"backendAddressPools\": [\n                    {\n                        \"name\": \"[variables('nfsHaLbBeName')]\"\n                    }\n                ],\n                \"loadBalancingRules\": [\n                    {\n                        \"name\": \"[concat(variables('nfsHaLbRuleName'), '-nfsd-tcp')]\",\n                        \"properties\": {\n                            \"frontendIPConfiguration\": {\n                                \"id\": \"[variables('nfsHaLbFeId')]\"\n                            },\n                            \"frontendPort\": 2049,\n                            \"backendAddressPool\": {\n                                \"id\": \"[variables('nfsHaLbBeId')]\"\n                            },\n                            \"backendPort\": 2049,\n                            \"protocol\": \"Tcp\",\n                            \"probe\": {\n                                \"id\": \"[variables('nfsHaLbProbeId')]\"\n                            },\n                            \"enableFloatingIP\": false,\n                            \"idleTimeoutInMinutes\": 4\n                        }\n                    },\n                    {\n                        \"name\": \"[concat(variables('nfsHaLbRuleName'), '-nfsd-udp')]\",\n                        \"properties\": {\n                            \"frontendIPConfiguration\": {\n                                \"id\": \"[variables('nfsHaLbFeId')]\"\n                            },\n                            \"frontendPort\": 2049,\n                            \"backendAddressPool\": {\n                                \"id\": \"[variables('nfsHaLbBeId')]\"\n                            },\n                            \"backendPort\": 2049,\n                            \"protocol\": \"Udp\",\n                            \"probe\": {\n                                \"id\": \"[variables('nfsHaLbProbeId')]\"\n                            },\n                            \"enableFloatingIP\": false,\n                            \"idleTimeoutInMinutes\": 4\n                        }\n                    },\n                    {\n                        \"name\": \"[concat(variables('nfsHaLbRuleName'), '-rpcbind-tcp')]\",\n                        \"properties\": {\n                            \"frontendIPConfiguration\": {\n                                \"id\": \"[variables('nfsHaLbFeId')]\"\n                            },\n                            \"frontendPort\": 111,\n                            \"backendAddressPool\": {\n                                \"id\": \"[variables('nfsHaLbBeId')]\"\n                            },\n                            \"backendPort\": 111,\n                            \"protocol\": \"Tcp\",\n                            \"probe\": {\n                                \"id\": \"[variables('nfsHaLbProbeId')]\"\n                            },\n                            \"enableFloatingIP\": false,\n                            \"idleTimeoutInMinutes\": 4\n                        }\n                    },\n                    {\n                        \"name\": \"[concat(variables('nfsHaLbRuleName'), '-rpcbind-udp')]\",\n                        \"properties\": {\n                            \"frontendIPConfiguration\": {\n                                \"id\": \"[variables('nfsHaLbFeId')]\"\n                            },\n                            \"frontendPort\": 111,\n                            \"backendAddressPool\": {\n                                \"id\": \"[variables('nfsHaLbBeId')]\"\n                            },\n                            \"backendPort\": 111,\n                            \"protocol\": \"Udp\",\n                            \"probe\": {\n                                \"id\": \"[variables('nfsHaLbProbeId')]\"\n                            },\n                            \"enableFloatingIP\": false,\n                            \"idleTimeoutInMinutes\": 4\n                        }\n                    },\n                    {\n                        \"name\": \"[concat(variables('nfsHaLbRuleName'), '-mountd-tcp')]\",\n                        \"properties\": {\n                            \"frontendIPConfiguration\": {\n                                \"id\": \"[variables('nfsHaLbFeId')]\"\n                            },\n                            \"frontendPort\": 2000,\n                            \"backendAddressPool\": {\n                                \"id\": \"[variables('nfsHaLbBeId')]\"\n                            },\n                            \"backendPort\": 2000,\n                            \"protocol\": \"Tcp\",\n                            \"probe\": {\n                                \"id\": \"[variables('nfsHaLbProbeId')]\"\n                            },\n                            \"enableFloatingIP\": false,\n                            \"idleTimeoutInMinutes\": 4\n                        }\n                    },\n                    {\n                        \"name\": \"[concat(variables('nfsHaLbRuleName'), '-mountd-udp')]\",\n                        \"properties\": {\n                            \"frontendIPConfiguration\": {\n                                \"id\": \"[variables('nfsHaLbFeId')]\"\n                            },\n                            \"frontendPort\": 2000,\n                            \"backendAddressPool\": {\n                                \"id\": \"[variables('nfsHaLbBeId')]\"\n                            },\n                            \"backendPort\": 2000,\n                            \"protocol\": \"Udp\",\n                            \"probe\": {\n                                \"id\": \"[variables('nfsHaLbProbeId')]\"\n                            },\n                            \"enableFloatingIP\": false,\n                            \"idleTimeoutInMinutes\": 4\n                        }\n                    },\n                    {\n                        \"name\": \"[concat(variables('nfsHaLbRuleName'), '-statd-tcp')]\",\n                        \"properties\": {\n                            \"frontendIPConfiguration\": {\n                                \"id\": \"[variables('nfsHaLbFeId')]\"\n                            },\n                            \"frontendPort\": 2001,\n                            \"backendAddressPool\": {\n                                \"id\": \"[variables('nfsHaLbBeId')]\"\n                            },\n                            \"backendPort\": 2001,\n                            \"protocol\": \"Tcp\",\n                            \"probe\": {\n                                \"id\": \"[variables('nfsHaLbProbeId')]\"\n                            },\n                            \"enableFloatingIP\": false,\n                            \"idleTimeoutInMinutes\": 4\n                        }\n                    },\n                    {\n                        \"name\": \"[concat(variables('nfsHaLbRuleName'), '-statd-udp')]\",\n                        \"properties\": {\n                            \"frontendIPConfiguration\": {\n                                \"id\": \"[variables('nfsHaLbFeId')]\"\n                            },\n                            \"frontendPort\": 2001,\n                            \"backendAddressPool\": {\n                                \"id\": \"[variables('nfsHaLbBeId')]\"\n                            },\n                            \"backendPort\": 2001,\n                            \"protocol\": \"Udp\",\n                            \"probe\": {\n                                \"id\": \"[variables('nfsHaLbProbeId')]\"\n                            },\n                            \"enableFloatingIP\": false,\n                            \"idleTimeoutInMinutes\": 4\n                        }\n                    },\n                    {\n                        \"name\": \"[concat(variables('nfsHaLbRuleName'), '-statd-outgoing-tcp')]\",\n                        \"properties\": {\n                            \"frontendIPConfiguration\": {\n                                \"id\": \"[variables('nfsHaLbFeId')]\"\n                            },\n                            \"frontendPort\": 2002,\n                            \"backendAddressPool\": {\n                                \"id\": \"[variables('nfsHaLbBeId')]\"\n                            },\n                            \"backendPort\": 2002,\n                            \"protocol\": \"Tcp\",\n                            \"probe\": {\n                                \"id\": \"[variables('nfsHaLbProbeId')]\"\n                            },\n                            \"enableFloatingIP\": false,\n                            \"idleTimeoutInMinutes\": 4\n                        }\n                    },\n                    {\n                        \"name\": \"[concat(variables('nfsHaLbRuleName'), '-statd-outgoing-udp')]\",\n                        \"properties\": {\n                            \"frontendIPConfiguration\": {\n                                \"id\": \"[variables('nfsHaLbFeId')]\"\n                            },\n                            \"frontendPort\": 2002,\n                            \"backendAddressPool\": {\n                                \"id\": \"[variables('nfsHaLbBeId')]\"\n                            },\n                            \"backendPort\": 2002,\n                            \"protocol\": \"Udp\",\n                            \"probe\": {\n                                \"id\": \"[variables('nfsHaLbProbeId')]\"\n                            },\n                            \"enableFloatingIP\": false,\n                            \"idleTimeoutInMinutes\": 4\n                        }\n                    },\n                    {\n                        \"name\": \"[concat(variables('nfsHaLbRuleName'), '-quotad-tcp')]\",\n                        \"properties\": {\n                            \"frontendIPConfiguration\": {\n                                \"id\": \"[variables('nfsHaLbFeId')]\"\n                            },\n                            \"frontendPort\": 2003,\n                            \"backendAddressPool\": {\n                                \"id\": \"[variables('nfsHaLbBeId')]\"\n                            },\n                            \"backendPort\": 2003,\n                            \"protocol\": \"Tcp\",\n                            \"probe\": {\n                                \"id\": \"[variables('nfsHaLbProbeId')]\"\n                            },\n                            \"enableFloatingIP\": false,\n                            \"idleTimeoutInMinutes\": 4\n                        }\n                    },\n                    {\n                        \"name\": \"[concat(variables('nfsHaLbRuleName'), '-quotad-udp')]\",\n                        \"properties\": {\n                            \"frontendIPConfiguration\": {\n                                \"id\": \"[variables('nfsHaLbFeId')]\"\n                            },\n                            \"frontendPort\": 2003,\n                            \"backendAddressPool\": {\n                                \"id\": \"[variables('nfsHaLbBeId')]\"\n                            },\n                            \"backendPort\": 2003,\n                            \"protocol\": \"Udp\",\n                            \"probe\": {\n                                \"id\": \"[variables('nfsHaLbProbeId')]\"\n                            },\n                            \"enableFloatingIP\": false,\n                            \"idleTimeoutInMinutes\": 4\n                        }\n                    },\n                    {\n                        \"name\": \"[concat(variables('nfsHaLbRuleName'), '-lockd-tcp')]\",\n                        \"properties\": {\n                            \"frontendIPConfiguration\": {\n                                \"id\": \"[variables('nfsHaLbFeId')]\"\n                            },\n                            \"frontendPort\": 2004,\n                            \"backendAddressPool\": {\n                                \"id\": \"[variables('nfsHaLbBeId')]\"\n                            },\n                            \"backendPort\": 2004,\n                            \"protocol\": \"Tcp\",\n                            \"probe\": {\n                                \"id\": \"[variables('nfsHaLbProbeId')]\"\n                            },\n                            \"enableFloatingIP\": false,\n                            \"idleTimeoutInMinutes\": 4\n                        }\n                    },\n                    {\n                        \"name\": \"[concat(variables('nfsHaLbRuleName'), '-lockd-udp')]\",\n                        \"properties\": {\n                            \"frontendIPConfiguration\": {\n                                \"id\": \"[variables('nfsHaLbFeId')]\"\n                            },\n                            \"frontendPort\": 2004,\n                            \"backendAddressPool\": {\n                                \"id\": \"[variables('nfsHaLbBeId')]\"\n                            },\n                            \"backendPort\": 2004,\n                            \"protocol\": \"Udp\",\n                            \"probe\": {\n                                \"id\": \"[variables('nfsHaLbProbeId')]\"\n                            },\n                            \"enableFloatingIP\": false,\n                            \"idleTimeoutInMinutes\": 4\n                        }\n                    },\n                    {\n                        \"name\": \"[concat(variables('nfsHaLbRuleName'), '-nfs-callback-tcp')]\",\n                        \"properties\": {\n                            \"frontendIPConfiguration\": {\n                                \"id\": \"[variables('nfsHaLbFeId')]\"\n                            },\n                            \"frontendPort\": 2005,\n                            \"backendAddressPool\": {\n                                \"id\": \"[variables('nfsHaLbBeId')]\"\n                            },\n                            \"backendPort\": 2005,\n                            \"protocol\": \"Tcp\",\n                            \"probe\": {\n                                \"id\": \"[variables('nfsHaLbProbeId')]\"\n                            },\n                            \"enableFloatingIP\": false,\n                            \"idleTimeoutInMinutes\": 4\n                        }\n                    },\n                    {\n                        \"name\": \"[concat(variables('nfsHaLbRuleName'), '-nfs-callback-udp')]\",\n                        \"properties\": {\n                            \"frontendIPConfiguration\": {\n                                \"id\": \"[variables('nfsHaLbFeId')]\"\n                            },\n                            \"frontendPort\": 2005,\n                            \"backendAddressPool\": {\n                                \"id\": \"[variables('nfsHaLbBeId')]\"\n                            },\n                            \"backendPort\": 2005,\n                            \"protocol\": \"Udp\",\n                            \"probe\": {\n                                \"id\": \"[variables('nfsHaLbProbeId')]\"\n                            },\n                            \"enableFloatingIP\": false,\n                            \"idleTimeoutInMinutes\": 4\n                        }\n                    }\n                ],\n                \"probes\": [\n                    {\n                        \"name\": \"[variables('nfsHaLbProbeName')]\",\n                        \"properties\": {\n                            \"intervalInSeconds\": 5,\n                            \"numberOfProbes\": 2,\n                            \"port\": 61000,\n                            \"protocol\": \"Tcp\"\n                        }\n                    }\n                ]\n            }\n        },\n        {\n            \"type\": \"Microsoft.Resources/deployments\",\n            \"apiVersion\": \"2017-05-10\",\n            \"copy\": {\n                \"count\": 2,\n                \"name\": \"nfs-ha-vm-loop\"\n            },\n            \"dependsOn\": [\n                \"[concat('Microsoft.Compute/availabilitySets/', variables('availSetName'))]\",\n                \"[concat('Microsoft.Network/loadBalancers/', variables('nfsHaLbName'))]\"\n            ],\n            \"name\": \"[concat(variables('vmDeploymentNameBase'), copyIndex())]\",\n            \"properties\": {\n                \"mode\": \"Incremental\",\n                \"parameters\": {\n                    \"_artifactsLocation\": {\n                        \"value\": \"[parameters('_artifactsLocation')]\"\n                    },\n                    \"_artifactsLocationSasToken\": {\n                        \"value\": \"[parameters('_artifactsLocationSasToken')]\"\n                    },\n                    \"location\": {\n                        \"value\": \"[parameters('location')]\"\n                    },\n                    \"vmIndex\": {\n                        \"value\": \"[copyindex()]\"\n                    },\n                    \"ipAddrs\": {\n                        \"value\": \"[variables('nodeIpAddrs')]\"\n                    },\n                    \"nfsClientsIPRange\": {\n                        \"value\": \"[parameters('nfsClientsIPRange')]\"\n                    },\n                    \"subnetId\": {\n                        \"value\": \"[parameters('subnetId')]\"\n                    },\n                    \"enableAccelNwSwitch\": {\n                        \"value\": \"[parameters('enableAccelNwSwitch')]\"\n                    },\n                    \"availSetId\": {\n                        \"value\": \"[resourceId('Microsoft.Compute/AvailabilitySets', variables('availSetName'))]\"\n                    },\n                    \"vmSku\": {\n                        \"value\": \"[parameters('vmSku')]\"\n                    },\n                    \"adminUserName\": {\n                        \"value\": \"[parameters('adminUserName')]\"\n                    },\n                    \"sshPublicKey\": {\n                        \"value\": \"[parameters('sshPublicKey')]\"\n                    },\n                    \"osType\": {\n                        \"value\": \"[parameters('osType')]\"\n                    },\n                    \"osDiskStorageType\": {\n                        \"value\": \"[parameters('osDiskStorageType')]\"\n                    },\n                    \"dataDiskCountPerVM\": {\n                        \"value\": \"[parameters('dataDiskCountPerVM')]\"\n                    },\n                    \"dataDiskSizeInGB\": {\n                        \"value\": \"[parameters('dataDiskSizeInGB')]\"\n                    },\n                    \"resourcesUniqueString\": {\n                        \"value\": \"[parameters('resourcesUniqueString')]\"\n                    },\n                    \"lbBeId\": {\n                        \"value\": \"[variables('nfsHaLbBeId')]\"\n                    }\n                },\n                \"templateLink\": {\n                    \"uri\": \"[concat(parameters('_artifactsLocation'), 'nested/nfs-ha-vm.json', parameters('_artifactsLocationSasToken'))]\"\n                }\n            }\n        }\n    ],\n    \"variables\": {\n        \"availSetName\": \"[concat('nfs-ha-availset-', parameters('resourcesUniqueString'))]\",\n        \"vmDeploymentNameBase\": \"nfs-ha-vm-deployment\",\n        \"nodeIpAddrs\": [\n            \"[parameters('node0IPAddr')]\",\n            \"[parameters('node1IPAddr')]\"\n        ],\n        \"nfsHaLbName\": \"[concat('nfs-ha-lb-', parameters('resourcesUniqueString'))]\",\n        \"nfsHaLbFeName\": \"nfs-ha-lb-fe\",\n        \"nfsHaLbFeId\": \"[resourceId('Microsoft.Network/loadBalancers/frontendIPConfigurations', variables('nfsHaLbName'), variables('nfsHaLbFeName'))]\",\n        \"nfsHaLbBeName\": \"nfs-ha-lb-be\",\n        \"nfsHaLbBeId\": \"[resourceId('Microsoft.Network/loadBalancers/backendAddressPools', variables('nfsHaLbName'), variables('nfsHaLbBeName'))]\",\n        \"nfsHaLbRuleName\": \"nfs-ha-lb-rule\",\n        \"nfsHaLbProbeName\": \"nfs-ha-lb-probe\",\n        \"nfsHaLbProbeId\": \"[resourceId('Microsoft.Network/loadBalancers/probes', variables('nfsHaLbName'), variables('nfsHaLbProbeName'))]\"\n    }\n}\n"
  },
  {
    "path": "nested/recoveryservices.json",
    "content": "{\n    \"$schema\": \"http://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#\",\n    \"contentVersion\": \"1.0.0.0\",\n    \"parameters\": {\n        \"moodleCommon\": {\n            \"metadata\": {\n                \"description\": \"Common Moodle values\"\n            },\n            \"type\": \"object\"\n        }\n    },\n    \"resources\": [\n        {\n            \"type\": \"Microsoft.RecoveryServices/vaults\",\n            \"apiVersion\": \"2016-06-01\",\n            \"location\": \"[parameters('moodleCommon').location]\",\n            \"name\": \"[parameters('moodleCommon').vaultName]\",\n            \"properties\": {},\n            \"sku\": {\n                \"name\": \"RS0\",\n                \"tier\": \"Standard\"\n            }\n        },\n        {\n            \"type\": \"Microsoft.RecoveryServices/vaults/backupPolicies\",\n            \"apiVersion\": \"2017-07-01\",\n            \"dependsOn\": [\n                \"[concat('Microsoft.RecoveryServices/vaults/', parameters('moodleCommon').vaultName)]\"\n            ],\n            \"location\": \"[parameters('moodleCommon').location]\",\n            \"name\": \"[concat(parameters('moodleCommon').vaultName, '/', parameters('moodleCommon').policyName)]\",\n            \"properties\": {\n                \"backupManagementType\": \"AzureIaasVM\",\n                \"retentionPolicy\": {\n                    \"dailySchedule\": {\n                        \"retentionDuration\": {\n                            \"count\": \"[variables(  'dailyRetentionDurationCount')]\",\n                            \"durationType\": \"Days\"\n                        },\n                        \"retentionTimes\": \"[variables('scheduleRunTimes')]\"\n                    },\n                    \"monthlySchedule\": {\n                        \"retentionDuration\": {\n                            \"count\": \"[variables('monthlyRetentionDurationCount')]\",\n                            \"durationType\": \"Months\"\n                        },\n                        \"retentionScheduleDaily\": {\n                            \"daysOfTheMonth\": [\n                                {\n                                    \"date\": 1,\n                                    \"isLast\": false\n                                }\n                            ]\n                        },\n                        \"retentionScheduleFormatType\": \"Daily\",\n                        \"retentionScheduleWeekly\": null,\n                        \"retentionTimes\": \"[variables('scheduleRunTimes')]\"\n                    },\n                    \"retentionPolicyType\": \"LongTermRetentionPolicy\",\n                    \"weeklySchedule\": {\n                        \"daysOfTheWeek\": \"[variables('daysOfTheWeek')]\",\n                        \"retentionDuration\": {\n                            \"count\": \"[variables( 'weeklyRetentionDurationCount')]\",\n                            \"durationType\": \"Weeks\"\n                        },\n                        \"retentionTimes\": \"[variables('scheduleRunTimes')]\"\n                    }\n                },\n                \"schedulePolicy\": {\n                    \"schedulePolicyType\": \"SimpleSchedulePolicy\",\n                    \"scheduleRunDays\": null,\n                    \"scheduleRunFrequency\": \"Daily\",\n                    \"scheduleRunTimes\": \"[variables('scheduleRunTimes')]\"\n                }\n            }\n        }\n    ],\n    \"variables\": {\n        \"dailyRetentionDurationCount\": 7,\n        \"daysOfTheWeek\": [\n            \"Sunday\"\n        ],\n        \"documentation1\": \"This sub-template creates a recovery services vault.  It expects certain values in the 'common' datastructure.\",\n        \"documentation2\": \" vaultName  - name of virtual network\",\n        \"documentation3\": \" policyName - name of backup policy inside vault\",\n        \"documentation4\": \"\",\n        \"documentation5\": \"The policy will create a daily backup with the following retentions\",\n        \"documentation6\": \" Daily   - keep last 7 daily\",\n        \"documentation7\": \" Weekly  - keep last 4 Sundays\",\n        \"documentation8\": \" Monthly - keep last 6 1st-of-the-month\",\n        \"monthlyRetentionDurationCount\": 6,\n        \"scheduleRunTimes\": [\n            \"2017-01-01T22:30:00Z\"\n        ],\n        \"weeklyRetentionDurationCount\": 4\n    }\n}\n"
  },
  {
    "path": "nested/recoveryservicesEnlist.json",
    "content": "{\n    \"$schema\": \"http://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#\",\n    \"contentVersion\": \"1.0.0.0\",\n    \"parameters\": {\n        \"moodleCommon\": {\n            \"metadata\": {\n                \"description\": \"Common Moodle values\"\n            },\n            \"type\": \"object\"\n        },\n        \"vmName\": {\n            \"metadata\": {\n                \"description\": \"Name of VM to enlist in AzureBackup\"\n            },\n            \"type\": \"string\"\n        }\n    },\n    \"resources\": [\n        {\n            \"type\": \"Microsoft.RecoveryServices/vaults/backupFabrics/protectionContainers/protectedItems\",\n            \"apiVersion\": \"2016-06-01\",\n            \"location\": \"[parameters('moodleCommon').location]\",\n            \"name\": \"[concat(parameters('moodleCommon').vaultName, '/', variables('backupFabric'), '/', variables('v2VmContainer'), concat(resourceGroup().name,';',parameters('vmName')), '/', variables('v2Vm'), concat(resourceGroup().name,';',parameters('vmName')))]\",\n            \"properties\": {\n                \"policyId\": \"[resourceId('Microsoft.RecoveryServices/vaults/backupPolicies',parameters('moodleCommon').vaultName,parameters('moodleCommon').policyName )]\",\n                \"protectedItemType\": \"[variables('v2VmType')]\",\n                \"sourceResourceId\": \"[resourceId(subscription().subscriptionId,resourceGroup().name,'Microsoft.Compute/virtualMachines',parameters('vmName'))]\"\n            }\n        }\n    ],\n    \"variables\": {\n        \"backupFabric\": \"Azure\",\n        \"documentation1\": \"This sub-template adds a VM to the recovery services vault.  It expects certain values in the 'common' datastructure.\",\n        \"documentation2\": \" vaultName  - name of virtual network\",\n        \"documentation3\": \" policyName - name of backup policy inside vault\",\n        \"documentation4\": \"\",\n        \"v2Vm\": \"vm;iaasvmcontainerv2;\",\n        \"v2VmContainer\": \"iaasvmcontainer;iaasvmcontainerv2;\",\n        \"v2VmType\": \"Microsoft.Compute/virtualMachines\"\n    }\n}\n"
  },
  {
    "path": "nested/redis.json",
    "content": "{\n    \"$schema\": \"http://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json\",\n    \"contentVersion\": \"1.0.0.0\",\n    \"parameters\": {\n        \"moodleCommon\": {\n            \"metadata\": {\n                \"description\": \"Common Moodle values\"\n            },\n            \"type\": \"object\"\n        },\n        \"subnetIdRedis\": {\n            \"metadata\": {\n                \"description\": \"Azure resource ID of the subnet where this Redis instance is to be deployed\"\n            },\n            \"type\": \"string\"\n        }\n    },\n    \"resources\": [\n        {\n            \"type\": \"Microsoft.Cache/Redis\",\n            \"apiVersion\": \"2016-04-01\",\n            \"location\": \"[parameters('moodleCommon').location]\",\n            \"name\": \"[parameters('moodleCommon').redisCacheName]\",\n            \"properties\": {\n                \"enableNonSslPort\": true,\n                \"sku\": {\n                    \"capacity\": 1,\n                    \"family\": \"P\",\n                    \"name\": \"Premium\"\n                },\n                \"subnetId\": \"[parameters('subnetIdRedis')]\"\n            }\n        }\n    ],\n    \"variables\": {\n        \"documentation1\": \"This sub-template creates a redis cache.  It expects certain values in the 'common' datastructure.\",\n        \"documentation2\": \" redisCacheName - name of cache\",\n        \"redisResourceId\": \"[resourceId('Microsoft.Cache/Redis', parameters('moodleCommon').redisCacheName)]\"\n    },\n    \"outputs\": {\n        \"redisKey\": {\n            \"value\": \"[listKeys(variables('redisResourceId'), '2016-04-01').primaryKey]\",\n            \"type\": \"string\"\n        }\n    }\n}\n"
  },
  {
    "path": "nested/search-azure.json",
    "content": "{\n    \"$schema\": \"http://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#\",\n    \"contentVersion\": \"1.0.0.0\",\n    \"parameters\": {\n        \"moodleCommon\": {\n            \"metadata\": {\n                \"description\": \"Common Moodle values\"\n            },\n            \"type\": \"object\"\n        },\n        \"subnetIdElastic\": {\n            \"metadata\": {\n                \"description\": \"Azure resource ID of the subnet where the Elastic Search VMs are to be deployed (if any)\"\n            },\n            \"type\": \"string\"\n        }\n    },\n    \"resources\": [\n        {\n            \"type\": \"Microsoft.Search/searchServices\",\n            \"apiVersion\": \"2015-08-19\",\n            \"location\": \"[parameters('moodleCommon').location]\",\n            \"name\": \"[parameters('moodleCommon').azureSearchName]\",\n            \"sku\": {\n                \"name\": \"[parameters('moodleCommon').azureSearchSku]\"\n            },\n            \"properties\": {\n                \"replicaCount\": \"[parameters('moodleCommon').azureSearchReplicaCount]\",\n                \"partitionCount\": \"[parameters('moodleCommon').azureSearchPartitionCount]\",\n                \"hostingMode\": \"[parameters('moodleCommon').azureSearchHostingMode]\"\n            }\n        }\n    ],\n    \"variables\": {\n        \"documentation01\": \"This sub-template drives the Azure Search which is used as the access-point for other moodle VM's \",\n        \"documentation02\": \"It expects certain values in the 'common' datastructure.\",\n        \"documentation03\": \"\tazureSearchName\t\t\t- the name of the Azure Search.\",\n        \"documentation04\": \"\tazureSearchSku\t\t\t- the level of Azure Search service.\",\n        \"documentation06\": \"\tazureSearchReplicaCount\t\t- number of Azure Search replicas.\",\n        \"documentation07\": \"\tazureSearchPartitionCount\t- number of Azure Search partitions.\",\n        \"documentation08\": \"\tazureSearchHostingMode\t\t- the type of Azure Search hosting mode.\",\n        \"azureSearchServiceId\": \"[resourceId('Microsoft.Search/searchServices', parameters('moodleCommon').azureSearchName)]\"\n    },\n    \"outputs\": {\n        \"azureSearchKey\": {\n            \"value\": \"[listAdminKeys(variables('azureSearchServiceId'), '2015-08-19').PrimaryKey]\",\n            \"type\": \"string\"\n        }\n    }\n}\n"
  },
  {
    "path": "nested/search-elastic-config.json",
    "content": "{\n    \"$schema\": \"http://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#\",\n    \"contentVersion\": \"1.0.0.0\",\n    \"parameters\": {\n        \"moodleCommon\": {\n            \"metadata\": {\n                \"description\": \"Common Moodle values\"\n            },\n            \"type\": \"object\"\n        }\n    },\n    \"resources\": [\n        {\n            \"type\": \"Microsoft.Compute/virtualMachines/extensions\",\n            \"apiVersion\": \"2017-03-30\",\n            \"location\": \"[parameters('moodleCommon').location]\",\n            \"name\": \"[concat(parameters('moodleCommon').elasticVmName1,'/','install_elastic')]\",\n            \"properties\": {\n                \"autoUpgradeMinorVersion\": true,\n                \"publisher\": \"Microsoft.Azure.Extensions\",\n                \"settings\": {\n                    \"fileUris\": [\n                        \"[variables('scriptUri')]\"\n                    ]\n                },\n                \"protectedSettings\":{\n                    \"commandToExecute\": \"[variables('cmdExec')]\"\n                },\n                \"type\": \"CustomScript\",\n                \"typeHandlerVersion\": \"2.0\"\n            },\n            \"tags\": {\n                \"displayName\": \"install_elastic\"\n            }\n        },\n        {\n            \"type\": \"Microsoft.Compute/virtualMachines/extensions\",\n            \"apiVersion\": \"2017-03-30\",\n            \"location\": \"[parameters('moodleCommon').location]\",\n            \"name\": \"[concat(parameters('moodleCommon').elasticVmName2,'/','install_elastic')]\",\n            \"properties\": {\n                \"autoUpgradeMinorVersion\": true,\n                \"publisher\": \"Microsoft.Azure.Extensions\",\n                \"settings\": {\n                    \"fileUris\": [\n                        \"[variables('scriptUri')]\"\n                    ]\n                },\n                \"protectedSettings\":{\n                    \"commandToExecute\": \"[variables('cmdExec')]\"\n                },\n                \"type\": \"CustomScript\",\n                \"typeHandlerVersion\": \"2.0\"\n            },\n            \"tags\": {\n                \"displayName\": \"install_elastic\"\n            }\n        },\n        {\n            \"type\": \"Microsoft.Compute/virtualMachines/extensions\",\n            \"apiVersion\": \"2017-03-30\",\n            \"location\": \"[parameters('moodleCommon').location]\",\n            \"name\": \"[concat(parameters('moodleCommon').elasticVmName3,'/','install_elastic')]\",\n            \"properties\": {\n                \"autoUpgradeMinorVersion\": true,\n                \"publisher\": \"Microsoft.Azure.Extensions\",\n                \"settings\": {\n                    \"fileUris\": [\n                        \"[variables('scriptUri')]\"\n                    ]\n                },\n                \"protectedSettings\":{\n                    \"commandToExecute\": \"[variables('cmdExec')]\"\n                },\n                \"type\": \"CustomScript\",\n                \"typeHandlerVersion\": \"2.0\"\n            },\n            \"tags\": {\n                \"displayName\": \"install_elastic\"\n            }\n        }\n    ],\n    \"variables\": {\n        \"cmdExec\": \"[concat('bash ', parameters('moodleCommon').elasticScriptFilename, ' ', parameters('moodleCommon').elasticClusterName, ' ', parameters('moodleCommon').elasticVm1IP, ' ', parameters('moodleCommon').elasticVm2IP, ' ', parameters('moodleCommon').elasticVm3IP)]\",\n        \"documentation01\": \"This sub-template applies a specific post-deployment script to the controller vm\",\n        \"documentation02\": \"It expects certain values in the 'common' datastructure.\",\n        \"documentation03\": \"    scriptLocation              - web URI\",\n        \"documentation04\": \"    elasticScriptFilename       - name of script file\",\n        \"documentation05\": \"    elasticVmName               - name of the elastic search vm generic name\",\n        \"scriptUri\": \"[concat(parameters('moodleCommon').scriptLocation,parameters('moodleCommon').elasticScriptFilename,parameters('moodleCommon').artifactsSasToken)]\"\n    }\n}\n"
  },
  {
    "path": "nested/search-elastic.json",
    "content": "{\n    \"$schema\": \"http://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#\",\n    \"contentVersion\": \"1.0.0.0\",\n    \"parameters\": {\n        \"moodleCommon\": {\n            \"metadata\": {\n                \"description\": \"Common Moodle values\"\n            },\n            \"type\": \"object\"\n        },\n        \"subnetIdElastic\": {\n            \"metadata\": {\n                \"description\": \"Azure resource ID of the subnet where the Elastic Search VMs are to be deployed (if any)\"\n            },\n            \"type\": \"string\"\n        }\n    },\n    \"resources\": [\n        {\n            \"type\": \"Microsoft.Network/networkInterfaces\",\n            \"apiVersion\": \"2017-10-01\",\n            \"location\": \"[parameters('moodleCommon').location]\",\n            \"name\": \"[parameters('moodleCommon').elasticNicName1]\",\n            \"properties\": {\n                \"ipConfigurations\": [\n                    {\n                        \"name\": \"ipcfg-elastic1\",\n                        \"properties\": {\n                            \"privateIPAllocationMethod\": \"Static\",\n                            \"privateIPAddress\": \"[parameters('moodleCommon').elasticVm1IP]\",\n                            \"subnet\": {\n                                \"id\": \"[parameters('subnetIdElastic')]\"\n                            }\n                        }\n                    }\n                ],\n                \"enableAcceleratedNetworking\": \"[parameters('moodleCommon').enableAccelNwForOtherVmsSwitch]\"\n            },\n            \"tags\": {\n                \"displayName\": \"Elastic NIC 1\"\n            }\n        },\n        {\n            \"type\": \"Microsoft.Compute/virtualMachines\",\n            \"apiVersion\": \"2017-03-30\",\n            \"dependsOn\": [\n                \"[concat('Microsoft.Network/networkInterfaces/', parameters('moodleCommon').elasticNicName1)]\"\n            ],\n            \"location\": \"[parameters('moodleCommon').location]\",\n            \"name\": \"[parameters('moodleCommon').elasticVmName1]\",\n            \"properties\": {\n                \"hardwareProfile\": {\n                    \"vmSize\": \"[parameters('moodleCommon').elasticVmSku]\"\n                },\n                \"networkProfile\": {\n                    \"networkInterfaces\": [\n                        {\n                            \"id\": \"[variables('nicRef1')]\"\n                        }\n                    ]\n                },\n                \"osProfile\": {\n                    \"adminUsername\": \"[parameters('moodleCommon').sshUsername]\",\n                    \"computerName\": \"[parameters('moodleCommon').elasticVmName1]\",\n                    \"linuxConfiguration\": {\n                        \"disablePasswordAuthentication\": true,\n                        \"ssh\": {\n                            \"publicKeys\": [\n                                {\n                                    \"path\": \"[concat('/home/', parameters('moodleCommon').sshUsername, '/.ssh/authorized_keys')]\",\n                                    \"keyData\": \"[parameters('moodleCommon').sshPublicKey]\"\n                                }\n                            ]\n                        }\n                    }\n                },\n                \"storageProfile\": {\n                    \"dataDisks\": [],\n                    \"imageReference\": \"[parameters('moodleCommon').osType]\",\n                    \"osDisk\": {\n                        \"createOption\": \"FromImage\",\n                        \"managedDisk\": {\n                            \"storageAccountType\": \"[parameters('moodleCommon').osDiskStorageType]\"\n                        },\n                        \"name\": \"[parameters('moodleCommon').elasticVmName1]\"\n                    }\n                }\n            },\n            \"tags\": {\n                \"displayName\": \"Elastic Search Virtual Machine\"\n            }\n        },\n        {\n            \"condition\": \"[parameters('moodleCommon').applyScriptsSwitch]\",\n            \"type\": \"Microsoft.Resources/deployments\",\n            \"apiVersion\": \"2017-05-10\",\n            \"dependsOn\": [\n                \"[concat('Microsoft.Compute/virtualMachines/', parameters('moodleCommon').elasticVmName1)]\"\n            ],\n            \"name\": \"[concat(parameters('moodleCommon').elasticVmName1,'-ScriptProcessor')]\",\n            \"properties\": {\n                \"mode\": \"Incremental\",\n                \"parameters\": {\n                    \"moodleCommon\": {\n                        \"value\": \"[parameters('moodleCommon')]\"\n                    }\n\n                },\n                \"templateLink\": {\n                    \"uri\": \"[concat(parameters('moodleCommon').baseTemplateUrl,'search-elastic-config.json',parameters('moodleCommon').artifactsSasToken)]\"\n                }\n            }\n        },\n        {\n            \"condition\": \"[parameters('moodleCommon').azureBackupSwitch]\",\n            \"type\": \"Microsoft.Resources/deployments\",\n            \"apiVersion\": \"2017-05-10\",\n            \"dependsOn\": [\n                \"[concat('Microsoft.Compute/virtualMachines/',parameters('moodleCommon').elasticVmName1)]\"\n            ],\n            \"name\": \"[concat(parameters('moodleCommon').elasticVmName1,'-Backup')]\",\n            \"properties\": {\n                \"mode\": \"Incremental\",\n                \"parameters\": {\n                    \"moodleCommon\": {\n                        \"value\": \"[parameters('moodleCommon')]\"\n                    },\n                    \"vmName\": {\n                        \"value\": \"[parameters('moodleCommon').elasticVmName1]\"\n                    }\n                },\n                \"templateLink\": {\n                    \"uri\": \"[concat(parameters('moodleCommon').baseTemplateUrl,'recoveryservicesEnlist.json',parameters('moodleCommon').artifactsSasToken)]\"\n                }\n            }\n        },\n        {\n            \"type\": \"Microsoft.Network/networkInterfaces\",\n            \"apiVersion\": \"2017-10-01\",\n            \"location\": \"[parameters('moodleCommon').location]\",\n            \"name\": \"[parameters('moodleCommon').elasticNicName2]\",\n            \"properties\": {\n                \"ipConfigurations\": [\n                    {\n                        \"name\": \"ipcfg-elastic2\",\n                        \"properties\": {\n                            \"privateIPAllocationMethod\": \"Static\",\n                            \"privateIPAddress\": \"[parameters('moodleCommon').elasticVm2IP]\",\n                            \"subnet\": {\n                                \"id\": \"[parameters('subnetIdElastic')]\"\n                            }\n                        }\n                    }\n                ],\n                \"enableAcceleratedNetworking\": \"[parameters('moodleCommon').enableAccelNwForOtherVmsSwitch]\"\n            },\n            \"tags\": {\n                \"displayName\": \"Elastic NIC 2\"\n            }\n        },\n        {\n            \"type\": \"Microsoft.Compute/virtualMachines\",\n            \"apiVersion\": \"2017-03-30\",\n            \"dependsOn\": [\n                \"[concat('Microsoft.Network/networkInterfaces/', parameters('moodleCommon').elasticNicName2)]\"\n            ],\n            \"location\": \"[parameters('moodleCommon').location]\",\n            \"name\": \"[parameters('moodleCommon').elasticVmName2]\",\n            \"properties\": {\n                \"hardwareProfile\": {\n                    \"vmSize\": \"[parameters('moodleCommon').elasticVmSku]\"\n                },\n                \"networkProfile\": {\n                    \"networkInterfaces\": [\n                        {\n                            \"id\": \"[variables('nicRef2')]\"\n                        }\n                    ]\n                },\n                \"osProfile\": {\n                    \"adminUsername\": \"[parameters('moodleCommon').sshUsername]\",\n                    \"computerName\": \"[parameters('moodleCommon').elasticVmName2]\",\n                    \"linuxConfiguration\": {\n                        \"disablePasswordAuthentication\": true,\n                        \"ssh\": {\n                            \"publicKeys\": [\n                                {\n                                    \"path\": \"[concat('/home/', parameters('moodleCommon').sshUsername, '/.ssh/authorized_keys')]\",\n                                    \"keyData\": \"[parameters('moodleCommon').sshPublicKey]\"\n                                }\n                            ]\n                        }\n                    }\n                },\n                \"storageProfile\": {\n                    \"dataDisks\": [],\n                    \"imageReference\": \"[parameters('moodleCommon').osType]\",\n                    \"osDisk\": {\n                        \"createOption\": \"FromImage\",\n                        \"managedDisk\": {\n                            \"storageAccountType\": \"[parameters('moodleCommon').osDiskStorageType]\"\n                        },\n                        \"name\": \"[parameters('moodleCommon').elasticVmName2]\"\n                    }\n                }\n            },\n            \"tags\": {\n                \"displayName\": \"Elastic Search Virtual Machine\"\n            }\n        },\n        {\n            \"condition\": \"[parameters('moodleCommon').applyScriptsSwitch]\",\n            \"type\": \"Microsoft.Resources/deployments\",\n            \"apiVersion\": \"2017-05-10\",\n            \"dependsOn\": [\n                \"[concat('Microsoft.Compute/virtualMachines/', parameters('moodleCommon').elasticVmName2)]\"\n            ],\n            \"name\": \"[concat(parameters('moodleCommon').elasticVmName2,'-ScriptProcessor')]\",\n            \"properties\": {\n                \"mode\": \"Incremental\",\n                \"parameters\": {\n                    \"moodleCommon\": {\n                        \"value\": \"[parameters('moodleCommon')]\"\n                    }\n\n                },\n                \"templateLink\": {\n                    \"uri\": \"[concat(parameters('moodleCommon').baseTemplateUrl,'search-elastic-config.json',parameters('moodleCommon').artifactsSasToken)]\"\n                }\n            }\n        },\n        {\n            \"condition\": \"[parameters('moodleCommon').azureBackupSwitch]\",\n            \"type\": \"Microsoft.Resources/deployments\",\n            \"apiVersion\": \"2017-05-10\",\n            \"dependsOn\": [\n                \"[concat('Microsoft.Compute/virtualMachines/',parameters('moodleCommon').elasticVmName2)]\"\n            ],\n            \"name\": \"[concat(parameters('moodleCommon').elasticVmName2,'-Backup')]\",\n            \"properties\": {\n                \"mode\": \"Incremental\",\n                \"parameters\": {\n                    \"moodleCommon\": {\n                        \"value\": \"[parameters('moodleCommon')]\"\n                    },\n                    \"vmName\": {\n                        \"value\": \"[parameters('moodleCommon').elasticVmName2]\"\n                    }\n                },\n                \"templateLink\": {\n                    \"uri\": \"[concat(parameters('moodleCommon').baseTemplateUrl,'recoveryservicesEnlist.json',parameters('moodleCommon').artifactsSasToken)]\"\n                }\n            }\n        },\n        {\n            \"type\": \"Microsoft.Network/networkInterfaces\",\n            \"apiVersion\": \"2017-10-01\",\n            \"location\": \"[parameters('moodleCommon').location]\",\n            \"name\": \"[parameters('moodleCommon').elasticNicName3]\",\n            \"properties\": {\n                \"ipConfigurations\": [\n                    {\n                        \"name\": \"ipcfg-elastic3\",\n                        \"properties\": {\n                            \"privateIPAllocationMethod\": \"Static\",\n                            \"privateIPAddress\": \"[parameters('moodleCommon').elasticVm3IP]\",\n                            \"subnet\": {\n                                \"id\": \"[parameters('subnetIdElastic')]\"\n                            }\n                        }\n                    }\n                ],\n                \"enableAcceleratedNetworking\": \"[parameters('moodleCommon').enableAccelNwForOtherVmsSwitch]\"\n            },\n            \"tags\": {\n                \"displayName\": \"Elastic NIC 2\"\n            }\n        },\n        {\n            \"type\": \"Microsoft.Compute/virtualMachines\",\n            \"apiVersion\": \"2017-03-30\",\n            \"dependsOn\": [\n                \"[concat('Microsoft.Network/networkInterfaces/', parameters('moodleCommon').elasticNicName3)]\"\n            ],\n            \"location\": \"[parameters('moodleCommon').location]\",\n            \"name\": \"[parameters('moodleCommon').elasticVmName3]\",\n            \"properties\": {\n                \"hardwareProfile\": {\n                    \"vmSize\": \"[parameters('moodleCommon').elasticVmSku]\"\n                },\n                \"networkProfile\": {\n                    \"networkInterfaces\": [\n                        {\n                            \"id\": \"[variables('nicRef3')]\"\n                        }\n                    ]\n                },\n                \"osProfile\": {\n                    \"adminUsername\": \"[parameters('moodleCommon').sshUsername]\",\n                    \"computerName\": \"[parameters('moodleCommon').elasticVmName3]\",\n                    \"linuxConfiguration\": {\n                        \"disablePasswordAuthentication\": true,\n                        \"ssh\": {\n                            \"publicKeys\": [\n                                {\n                                    \"path\": \"[concat('/home/', parameters('moodleCommon').sshUsername, '/.ssh/authorized_keys')]\",\n                                    \"keyData\": \"[parameters('moodleCommon').sshPublicKey]\"\n                                }\n                            ]\n                        }\n                    }\n                },\n                \"storageProfile\": {\n                    \"dataDisks\": [],\n                    \"imageReference\": \"[parameters('moodleCommon').osType]\",\n                    \"osDisk\": {\n                        \"createOption\": \"FromImage\",\n                        \"managedDisk\": {\n                            \"storageAccountType\": \"[parameters('moodleCommon').osDiskStorageType]\"\n                        },\n                        \"name\": \"[parameters('moodleCommon').elasticVmName3]\"\n                    }\n                }\n            },\n            \"tags\": {\n                \"displayName\": \"Elastic Search Virtual Machine\"\n            }\n        },\n        {\n            \"condition\": \"[parameters('moodleCommon').applyScriptsSwitch]\",\n            \"type\": \"Microsoft.Resources/deployments\",\n            \"apiVersion\": \"2017-05-10\",\n            \"dependsOn\": [\n                \"[concat('Microsoft.Compute/virtualMachines/', parameters('moodleCommon').elasticVmName3)]\"\n            ],\n            \"name\": \"[concat(parameters('moodleCommon').elasticVmName3,'-ScriptProcessor')]\",\n            \"properties\": {\n                \"mode\": \"Incremental\",\n                \"parameters\": {\n                    \"moodleCommon\": {\n                        \"value\": \"[parameters('moodleCommon')]\"\n                    }\n\n                },\n                \"templateLink\": {\n                    \"uri\": \"[concat(parameters('moodleCommon').baseTemplateUrl,'search-elastic-config.json',parameters('moodleCommon').artifactsSasToken)]\"\n                }\n            }\n        },\n        {\n            \"condition\": \"[parameters('moodleCommon').azureBackupSwitch]\",\n            \"type\": \"Microsoft.Resources/deployments\",\n            \"apiVersion\": \"2017-05-10\",\n            \"dependsOn\": [\n                \"[concat('Microsoft.Compute/virtualMachines/',parameters('moodleCommon').elasticVmName3)]\"\n            ],\n            \"name\": \"[concat(parameters('moodleCommon').elasticVmName3,'-Backup')]\",\n            \"properties\": {\n                \"mode\": \"Incremental\",\n                \"parameters\": {\n                    \"moodleCommon\": {\n                        \"value\": \"[parameters('moodleCommon')]\"\n                    },\n                    \"vmName\": {\n                        \"value\": \"[parameters('moodleCommon').elasticVmName3]\"\n                    }\n                },\n                \"templateLink\": {\n                    \"uri\": \"[concat(parameters('moodleCommon').baseTemplateUrl,'recoveryservicesEnlist.json',parameters('moodleCommon').artifactsSasToken)]\"\n                }\n            }\n        }\n    ],\n    \"variables\": {\n        \"documentation01\": \"This sub-template drives the elastic which is used as the access-point for other moodle VM's \",\n        \"documentation02\": \"It expects certain values in the 'common' datastructure.\",\n        \"documentation03\": \"    vnetName               - name of the virtual network\",\n        \"documentation04\": \"    subnetElastic          - name of subnet for elastic (and vm scale set)\",\n        \"documentation06\": \"    elasticNicName1        - name of the eastlic vm 1 network interface\",\n        \"documentation07\": \"    elasticNicName2        - name of the eastlic vm 2 network interface\",\n        \"documentation08\": \"    elasticNicName3        - name of the eastlic vm 3 network interface\",\n        \"documentation09\": \"    elasticVmName1         - name of the eastlic vm 1\",\n        \"documentation10\": \"    elasticVmName2         - name of the eastlic vm 2\",\n        \"documentation11\": \"    elasticVmName3         - name of the eastlic vm 3\",\n        \"documentation12\": \"    elasticVm1IP           - IP of the eastlic vm 1\",\n        \"documentation13\": \"    elasticVm2IP           - IP of the eastlic vm 2\",\n        \"documentation14\": \"    elasticVm3IP           - IP of the eastlic vm 3\",\n        \"documentation15\": \"This sub-template calls other sub-templates\",\n        \"documentation16\": \"    elasticconfig          - conditionally applies post-deployment script on the VM\",\n        \"documentation17\": \"    recoveryservicesEnlist - conditionally enlists the VM into the backup regimen\",\n        \"nicRef1\": \"[resourceId('Microsoft.Network/networkInterfaces', parameters('moodleCommon').elasticNicName1)]\",\n        \"nicRef2\": \"[resourceId('Microsoft.Network/networkInterfaces', parameters('moodleCommon').elasticNicName2)]\",\n        \"nicRef3\": \"[resourceId('Microsoft.Network/networkInterfaces', parameters('moodleCommon').elasticNicName3)]\"\n    }\n}\n"
  },
  {
    "path": "nested/storageAccount.json",
    "content": "{\n    \"$schema\": \"https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#\",\n    \"contentVersion\": \"1.0.0.0\",\n    \"parameters\": {\n        \"moodleCommon\": {\n            \"metadata\": {\n                \"description\": \"Common Moodle values\"\n            },\n            \"type\": \"object\"\n        }\n    },\n    \"resources\": [\n        {\n            \"condition\": \"[not(equals(parameters('moodleCommon').fileServerType, 'azurefiles'))]\",\n            \"type\": \"Microsoft.Storage/storageAccounts\",\n            \"apiVersion\": \"2019-06-01\",\n            \"location\": \"[parameters('moodleCommon').location]\",\n            \"name\": \"[concat(parameters('moodleCommon').storageAccountName,'naf')]\",\n            \"kind\": \"Storage\",\n            \"sku\": {\n                \"name\": \"[parameters('moodleCommon').storageAccountType]\"\n            },\n            \"properties\": {\n                \"encryption\": {\n                    \"keySource\": \"Microsoft.Storage\",\n                    \"services\": {\n                        \"blob\": {\n                            \"enabled\": true\n                        },\n                        \"file\": {\n                            \"enabled\": true\n                        }\n                    }\n                },\n                \"networkAcls\": {\n                    \"bypass\": \"AzureServices\",\n                    \"defaultAction\": \"Allow\",\n                    \"ipRules\": [],\n                    \"virtualNetworkRules\": []\n                },\n                \"supportsHttpsTrafficOnly\": true\n            }\n        },\n        {\n            \"condition\": \"[equals(parameters('moodleCommon').fileServerType, 'azurefiles')]\",\n            \"type\": \"Microsoft.Storage/storageAccounts\",\n            \"apiVersion\": \"2019-06-01\",\n            \"location\": \"[parameters('moodleCommon').location]\",\n            \"name\": \"[concat(parameters('moodleCommon').storageAccountName,'af')]\",\n            \"kind\": \"[if(equals(parameters('moodleCommon').storageAccountType, 'Premium_LRS'), 'FileStorage', 'Storage')]\",\n            \"sku\": {\n                \"name\": \"[parameters('moodleCommon').storageAccountType]\"\n            },\n            \"properties\": {\n                \"largeFileSharesState\": \"Enabled\",\n                \"networkAcls\": {\n                    \"bypass\": \"AzureServices\",\n                    \"virtualNetworkRules\": [],\n                    \"ipRules\": [],\n                    \"defaultAction\": \"Allow\"\n                },\n                \"supportsHttpsTrafficOnly\": true,\n                \"encryption\": {\n                    \"services\": {\n                        \"file\": {\n                            \"keyType\": \"Account\",\n                            \"enabled\": true\n                        },\n                        \"blob\": {\n                            \"keyType\": \"Account\",\n                            \"enabled\": true\n                        }\n                    },\n                    \"keySource\": \"Microsoft.Storage\"\n                }\n            }\n        }\n    ],\n    \"variables\": {\n        \"documentation1\": \"This sub-template creates a storage account.  It expects certain values in the 'common' datastructure.\",\n        \"documentation2\": \" storageAccountName - name of storage account\",\n        \"documentation3\": \" storageAccountType - type of storage account\",\n        \"storageName\": \"[concat(parameters('moodleCommon').storageAccountName,if(equals(parameters('moodleCommon').fileServerType, 'azurefiles'), 'af', 'naf'))]\",\n        \"storageAccountId\": \"[resourceId('Microsoft.Storage/storageAccounts', variables('storageName'))]\"\n    },\n    \"outputs\": {\n        \"storageAccountKey\": {\n            \"value\": \"[listKeys(variables('storageAccountId'), '2019-06-01').keys[0].value]\",\n            \"type\": \"string\"\n        },\n        \"storageAccountName\": {\n            \"value\": \"[variables('storageName')]\",\n            \"type\": \"string\"\n        }\n    }\n}\n"
  },
  {
    "path": "nested/tika.json",
    "content": "{\n    \"$schema\": \"http://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#\",\n    \"contentVersion\": \"1.0.0.0\",\n    \"parameters\": {\n        \"moodleCommon\": {\n            \"metadata\": {\n                \"description\": \"Common Moodle values\"\n            },\n            \"type\": \"object\"\n        },\n        \"subnetIdTika\": {\n            \"metadata\": {\n                \"description\": \"Azure resource ID of the subnet where this Tika VM is to be deployed\"\n            },\n            \"type\": \"string\"\n        }\n    },\n    \"resources\": [\n        {\n            \"type\": \"Microsoft.Network/networkInterfaces\",\n            \"apiVersion\": \"2017-10-01\",\n            \"location\": \"[parameters('moodleCommon').location]\",\n            \"name\": \"[parameters('moodleCommon').tikaNicName]\",\n            \"properties\": {\n                \"ipConfigurations\": [\n                    {\n                        \"name\": \"ipcfg-tika\",\n                        \"properties\": {\n                            \"privateIPAllocationMethod\": \"Static\",\n                            \"privateIPAddress\": \"[parameters('moodleCommon').tikaVmIP]\",\n                            \"subnet\": {\n                                \"id\": \"[parameters('subnetIdTika')]\"\n                            }\n                        }\n                    }\n                ],\n                \"enableAcceleratedNetworking\": \"[parameters('moodleCommon').enableAccelNwForOtherVmsSwitch]\"\n            },\n            \"tags\": {\n                \"displayName\": \"Tika NIC\"\n            }\n        },\n        {\n            \"type\": \"Microsoft.Compute/virtualMachines\",\n            \"apiVersion\": \"2017-03-30\",\n            \"dependsOn\": [\n                \"[concat('Microsoft.Network/networkInterfaces/', parameters('moodleCommon').tikaNicName)]\"\n            ],\n            \"location\": \"[parameters('moodleCommon').location]\",\n            \"name\": \"[parameters('moodleCommon').tikaVmName]\",\n            \"properties\": {\n                \"hardwareProfile\": {\n                    \"vmSize\": \"[parameters('moodleCommon').tikaVmSku]\"\n                },\n                \"networkProfile\": {\n                    \"networkInterfaces\": [\n                        {\n                            \"id\": \"[variables('nicRef')]\"\n                        }\n                    ]\n                },\n                \"osProfile\": {\n                    \"adminUsername\": \"[parameters('moodleCommon').sshUsername]\",\n                    \"computerName\": \"[parameters('moodleCommon').tikaVmName]\",\n                    \"linuxConfiguration\": {\n                        \"disablePasswordAuthentication\": true,\n                        \"ssh\": {\n                            \"publicKeys\": [\n                                {\n                                    \"path\": \"[concat('/home/', parameters('moodleCommon').sshUsername, '/.ssh/authorized_keys')]\",\n                                    \"keyData\": \"[parameters('moodleCommon').sshPublicKey]\"\n                                }\n                            ]\n                        }\n                    }\n                },\n                \"storageProfile\": {\n                    \"dataDisks\": [],\n                    \"imageReference\": \"[parameters('moodleCommon').osType]\",\n                    \"osDisk\": {\n                        \"createOption\": \"FromImage\",\n                        \"managedDisk\": {\n                            \"storageAccountType\": \"[parameters('moodleCommon').osDiskStorageType]\"\n                        },\n                        \"name\": \"[parameters('moodleCommon').tikaVmName]\"\n                    }\n                }\n            },\n            \"tags\": {\n                \"displayName\": \"Tika Service Virtual Machine\"\n            }\n        },\n        {\n            \"condition\": \"[parameters('moodleCommon').applyScriptsSwitch]\",\n            \"type\": \"Microsoft.Resources/deployments\",\n            \"apiVersion\": \"2017-05-10\",\n            \"dependsOn\": [\n                \"[concat('Microsoft.Compute/virtualMachines/', parameters('moodleCommon').tikaVmName)]\"\n            ],\n            \"name\": \"[concat(parameters('moodleCommon').tikaVmName,'-ScriptProcessor')]\",\n            \"properties\": {\n                \"mode\": \"Incremental\",\n                \"parameters\": {\n                    \"moodleCommon\": {\n                        \"value\": \"[parameters('moodleCommon')]\"\n                    }\n\n                },\n                \"templateLink\": {\n                    \"uri\": \"[concat(parameters('moodleCommon').baseTemplateUrl,'tikaconfig.json',parameters('moodleCommon').artifactsSasToken)]\"\n                }\n            }\n        },\n        {\n            \"condition\": \"[parameters('moodleCommon').azureBackupSwitch]\",\n            \"type\": \"Microsoft.Resources/deployments\",\n            \"apiVersion\": \"2017-05-10\",\n            \"dependsOn\": [\n                \"[concat('Microsoft.Compute/virtualMachines/',parameters('moodleCommon').tikaVmName)]\"\n            ],\n            \"name\": \"[concat(parameters('moodleCommon').tikaVmName,'-Backup')]\",\n            \"properties\": {\n                \"mode\": \"Incremental\",\n                \"parameters\": {\n                    \"moodleCommon\": {\n                        \"value\": \"[parameters('moodleCommon')]\"\n                    },\n                    \"vmName\": {\n                        \"value\": \"[parameters('moodleCommon').tikaVmName]\"\n                    }\n                },\n                \"templateLink\": {\n                    \"uri\": \"[concat(parameters('moodleCommon').baseTemplateUrl,'recoveryservicesEnlist.json',parameters('moodleCommon').artifactsSasToken)]\"\n                }\n            }\n        }\n    ],\n    \"variables\": {\n        \"documentation01\": \"This sub-template drives the tika service which is used as the access-point for moodle VM's when using tika search or azure search\",\n        \"documentation02\": \"It expects certain values in the 'common' datastructure.\",\n        \"documentation03\": \"    vnetName               - name of the virtual network\",\n        \"documentation04\": \"    subnetTika             - name of subnet for tika (and vm scale set)\",\n        \"documentation06\": \"    tikaNicName            - name of the tika vm network interface\",\n        \"documentation11\": \"    tikaVmName             - name of the tika vm\",\n        \"documentation12\": \"    tikaVmIP               - IP of the tika vm\",\n        \"documentation15\": \"This sub-template calls other sub-templates\",\n        \"documentation16\": \"    tikaconfig             - conditionally applies post-deployment script on the VM\",\n        \"documentation17\": \"    recoveryservicesEnlist - conditionally enlists the VM into the backup regimen\",\n        \"nicRef\": \"[resourceId('Microsoft.Network/networkInterfaces', parameters('moodleCommon').tikaNicName)]\"\n    }\n}\n"
  },
  {
    "path": "nested/tikaconfig.json",
    "content": "{\n    \"$schema\": \"http://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#\",\n    \"contentVersion\": \"1.0.0.0\",\n    \"parameters\": {\n        \"moodleCommon\": {\n            \"metadata\": {\n                \"description\": \"Common Moodle values\"\n            },\n            \"type\": \"object\"\n        }\n    },\n    \"resources\": [\n        {\n            \"type\": \"Microsoft.Compute/virtualMachines/extensions\",\n            \"apiVersion\": \"2017-03-30\",\n            \"location\": \"[parameters('moodleCommon').location]\",\n            \"name\": \"[concat(parameters('moodleCommon').tikaVmName,'/','install_tika')]\",\n            \"properties\": {\n                \"autoUpgradeMinorVersion\": true,\n                \"publisher\": \"Microsoft.Azure.Extensions\",\n                \"settings\": {\n                    \"fileUris\": [\n                        \"[variables('scriptUri')]\"\n                    ]\n                },\n                \"protectedSettings\":{\n                    \"commandToExecute\": \"[variables('cmdExec')]\"\n                },\n                \"type\": \"CustomScript\",\n                \"typeHandlerVersion\": \"2.0\"\n            },\n            \"tags\": {\n                \"displayName\": \"install_tika\"\n            }\n        }\n    ],\n    \"variables\": {\n        \"cmdExec\": \"[concat('bash ', parameters('moodleCommon').tikaScriptFilename, ' ', parameters('moodleCommon').tikaVmIP)]\",\n        \"documentation01\": \"This sub-template applies a specific post-deployment script to the tika vm\",\n        \"documentation02\": \"It expects certain values in the 'common' datastructure.\",\n        \"documentation03\": \"    scriptLocation              - web URI\",\n        \"documentation04\": \"    tikaScriptFilename          - name of script file\",\n        \"documentation05\": \"    tikaVmName                  - name of the tika search vm generic name\",\n        \"scriptUri\": \"[concat(parameters('moodleCommon').scriptLocation,parameters('moodleCommon').tikaScriptFilename,parameters('moodleCommon').artifactsSasToken)]\"\n    }\n}\n"
  },
  {
    "path": "nested/vmsetupparams.json",
    "content": "{\n    \"$schema\": \"http://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#\",\n    \"contentVersion\": \"1.0.0.0\",\n    \"parameters\": {\n        \"moodleCommon\": {\n            \"metadata\": {\n                \"description\": \"Common Moodle values\"\n            },\n            \"type\": \"object\"\n        },\n        \"dbFQDN\": {\n            \"metadata\": {\n                \"description\": \"FQDN of the deployed SQL DB\"\n            },\n            \"type\": \"string\"\n        },\n        \"storageAccountName\": {\n            \"metadata\": {\n                \"description\": \"Storage account name from the storage account deployment.\"\n            },\n            \"type\": \"string\"\n        },\n        \"storageAccountKey\": {\n            \"metadata\": {\n                \"description\": \"Storage account key from the storage account deployment.\"\n            },\n            \"type\": \"string\"\n        },\n        \"redisKey\": {\n            \"metadata\": {\n                \"description\": \"Redis cache key from the redis deployment ('None' if redis is not selected). This just gets passed through to the controllersetup.json.\"\n            },\n            \"type\": \"string\"\n        },\n        \"azureSearchKey\": {\n            \"metadata\": {\n                \"description\": \"Azure Search primary key from the Azure Search service deployment ('None' if Azure Search service is not selected)\"\n            },\n            \"type\": \"string\"\n        }\n    },\n    \"resources\": [],\n    \"variables\": {\n        \"documentation01\": \"This sub-template doesn't create any Azure resource, but just constructs/returns a JSON object that'll be injected to controller & VMSS VMs (through cloud-init) so that VM setup custom script can read/use, instead of receiving these as a long list of cmdline args\",\n        \"vmSetupParamsObj\": {\n            \"siteProfile\": {\n                \"siteURL\": \"[parameters('moodleCommon').siteURL]\",\n                \"httpsTermination\": \"[parameters('moodleCommon').httpsTermination]\",\n                \"thumbprintSslCert\": \"[parameters('moodleCommon').thumbprintSslCert]\",\n                \"thumbprintCaCert\": \"[parameters('moodleCommon').thumbprintCaCert]\"\n            },\n            \"moodleProfile\": {\n                \"version\": \"[parameters('moodleCommon').moodleVersion]\",\n                \"dbName\": \"[parameters('moodleCommon').moodleDbName]\",\n                \"dbUser\": \"[parameters('moodleCommon').moodleDbUser]\",\n                \"dbUserAzure\": \"[parameters('moodleCommon').moodleDbUserAzure]\",\n                \"dbPassword\": \"[parameters('moodleCommon').moodleDbPass]\",\n                \"adminPassword\": \"[parameters('moodleCommon').moodleAdminPass]\",\n                \"storageAccountName\": \"[parameters('storageAccountName')]\",\n                \"storageAccountKey\": \"[parameters('storageAccountKey')]\",\n                \"storageAccountType\": \"[parameters('moodleCommon').storageAccountType]\",\n                \"redisDns\": \"[parameters('moodleCommon').redisDns]\",\n                \"redisKey\": \"[parameters('redisKey')]\",\n                \"elasticVm1IP\": \"[parameters('moodleCommon').elasticVm1IP]\",\n                \"installO365pluginsSwitch\": \"[parameters('moodleCommon').installO365pluginsSwitch]\",\n                \"installObjectFsSwitch\": \"[parameters('moodleCommon').installObjectFsSwitch]\",\n                \"installGdprPluginsSwitch\": \"[parameters('moodleCommon').installGdprPluginsSwitch]\",\n                \"searchType\": \"[parameters('moodleCommon').searchType]\",\n                \"azureSearchKey\": \"[parameters('azureSearchKey')]\",\n                \"azureSearchNameHost\": \"[parameters('moodleCommon').azureSearchNameHost]\",\n                \"tikaVmIP\": \"[parameters('moodleCommon').tikaVmIP]\",\n                \"syslogServer\": \"[parameters('moodleCommon').ctlrVmName]\",\n                \"webServerType\": \"[parameters('moodleCommon').webServerType]\",\n                \"htmlLocalCopySwitch\": \"[parameters('moodleCommon').htmlLocalCopySwitch]\",\n                \"isMigration\": \"[parameters('moodleCommon').isMigration]\"\n            },\n            \"dbServerProfile\": {\n                \"type\": \"[if(equals(parameters('moodleCommon').dbServerType, 'mysqlflex'), 'mysql', parameters('moodleCommon').dbServerType)]\",\n                \"fqdn\": \"[parameters('dbFQDN')]\",\n                \"adminLogin\": \"[parameters('moodleCommon').dbLogin]\",\n                \"adminLoginAzure\": \"[if(equals(parameters('moodleCommon').dbServerType, 'mysqlflex'), parameters('moodleCommon').dbLogin, concat(parameters('moodleCommon').dbLogin, '@', parameters('moodleCommon').dbServerType, '-', parameters('moodleCommon').resourcesPrefix))]\",\n                \"adminPassword\": \"[parameters('moodleCommon').dbLoginPassword]\",\n                \"mssqlDbServiceObjectiveName\": \"[parameters('moodleCommon').mssqlDbServiceObjectiveName]\",\n                \"mssqlDbEdition\": \"[parameters('moodleCommon').mssqlDbEdition]\",\n                \"mssqlDbSize\": \"[parameters('moodleCommon').mssqlDbSize]\"\n            },\n            \"fileServerProfile\": {\n                \"type\": \"[parameters('moodleCommon').fileServerType]\",\n                \"nfsVmName\": \"[parameters('moodleCommon').ctlrVmName]\",\n                \"glusterVmName\": \"[concat(parameters('moodleCommon').gfsNameRoot, '0')]\",\n                \"glusterVolName\": \"data\",\n                \"nfsByoIpExportPath\": \"[parameters('moodleCommon').nfsByoIpExportPath]\",\n                \"nfsHaLbIP\": \"[parameters('moodleCommon').nfsHaLbIP]\",\n                \"nfsHaExportPath\": \"[parameters('moodleCommon').nfsHaExportPath]\",\n                \"fileServerDiskSize\": \"[parameters('moodleCommon').fileServerDiskSize]\"\n            },\n            \"phpProfile\": {\n                \"phpVersion\": \"[parameters('moodleCommon').phpVersion]\"\n            }\n        }\n    },\n    \"outputs\": {\n        \"vmSetupParamsObj\": {\n            \"value\": \"[variables('vmSetupParamsObj')]\",\n            \"type\": \"object\"\n        }\n    }\n}\n"
  },
  {
    "path": "nested/webvmss.json",
    "content": "{\n    \"$schema\": \"https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#\",\n    \"contentVersion\": \"1.0.0.0\",\n    \"parameters\": {\n        \"moodleCommon\": {\n            \"metadata\": {\n                \"description\": \"Common Moodle values\"\n            },\n            \"type\": \"object\"\n        },\n        \"subnetIdWeb\": {\n            \"metadata\": {\n                \"description\": \"Azure resource ID of the subnet where this VMSS is to be deployed\"\n            },\n            \"type\": \"string\"\n        },\n        \"vmSetupParamsObj\": {\n            \"metadata\": {\n                \"description\": \"JSON-structured VM setup params that'll be injected to the VM (through cloud-init) and used by the custom script (setup_webserver.sh)\"\n            },\n            \"type\": \"object\"\n        }\n    },\n    \"resources\": [\n            {\n            \"type\": \"Microsoft.Network/networkSecurityGroups\",\n            \"apiVersion\": \"2017-10-01\",\n            \"location\": \"[parameters('moodleCommon').location]\",\n            \"name\": \"[parameters('moodleCommon').vmssNsgName]\",\n            \"properties\": {\n                \"securityRules\": [\n                    {\n                        \"name\": \"Allow_http\",\n                        \"properties\": {\n                            \"access\": \"Allow\",\n                            \"destinationAddressPrefix\": \"*\",\n                            \"destinationPortRange\": \"80\",\n                            \"direction\": \"Inbound\",\n                            \"priority\": 1000,\n                            \"protocol\": \"Tcp\",\n                            \"sourceAddressPrefix\": \"*\",\n                            \"sourcePortRange\": \"*\"\n                        }\n                    },\n                    {\n                        \"name\": \"Allow_https\",\n                        \"properties\": {\n                            \"access\": \"Allow\",\n                            \"destinationAddressPrefix\": \"*\",\n                            \"destinationPortRange\": \"443\",\n                            \"direction\": \"Inbound\",\n                            \"priority\": 1005,\n                            \"protocol\": \"Tcp\",\n                            \"sourceAddressPrefix\": \"*\",\n                            \"sourcePortRange\": \"*\"\n                        }\n                    }\n                ]\n            },\n            \"tags\": {\n                \"displayName\": \"VMSS NSG\"\n            }\n        },\n        {\n            \"type\": \"Microsoft.Storage/storageAccounts\",\n            \"apiVersion\": \"2019-06-01\",\n            \"location\": \"[parameters('moodleCommon').location]\",\n            \"name\": \"[parameters('moodleCommon').vmssdStorageAccounttName]\",\n            \"kind\": \"Storage\",\n            \"sku\": {\n                \"name\": \"Standard_LRS\"\n            }\n        },\n        {\n            \"type\": \"Microsoft.Compute/virtualMachineScaleSets\",\n            \"apiVersion\": \"2019-07-01\",\n            \"dependsOn\": [\n                \"[concat('Microsoft.Storage/storageAccounts/', parameters('moodleCommon').vmssdStorageAccounttName)]\",\n                \"[concat('Microsoft.Network/networkSecurityGroups/', parameters('moodleCommon').vmssNsgName)]\"\n            ],\n            \"location\": \"[parameters('moodleCommon').location]\",\n            \"name\": \"[parameters('moodleCommon').vmssName]\",\n            \"properties\": {\n                \"overprovision\": true,\n                \"upgradePolicy\": {\n                    \"mode\": \"Manual\"\n                },\n                \"virtualMachineProfile\": {\n                    \"extensionProfile\": {\n                        \"extensions\": [\n                            {\n                                \"name\": \"setup_moodle\",\n                                \"properties\": {\n                                    \"autoUpgradeMinorVersion\": true,\n                                    \"publisher\": \"Microsoft.Azure.Extensions\",\n                                    \"settings\": {\n                                        \"fileUris\": [\n                                            \"[variables('scriptUri')]\",\n                                            \"[parameters('moodleCommon').commonFunctionsScriptUri]\"\n                                        ]\n                                    },\n                                    \"protectedSettings\": {\n                                        \"commandToExecute\": \"[concat('bash ', parameters('moodleCommon').webServerSetupScriptFilename, ' ', parameters('moodleCommon').moodleOnAzureConfigsJsonPath)]\"\n                                    },\n                                    \"type\": \"CustomScript\",\n                                    \"typeHandlerVersion\": \"2.0\"\n                                }\n                            }\n                        ]\n                    },\n                    \"networkProfile\": {\n                        \"networkInterfaceConfigurations\": [\n                            {\n                                \"name\": \"vmssnic\",\n                                \"properties\": {\n                                    \"ipConfigurations\": [\n                                        {\n                                            \"name\": \"ipcfg_lb\",\n                                            \"properties\": {\n                                                \"loadBalancerBackendAddressPools\": \"[take(variables('lbBePoolArray'), variables('lbBePoolArrayTakeCount'))]\",\n                                                \"applicationGatewayBackendAddressPools\": \"[take(variables('appGwBePoolArray'), variables('appGwBePoolArrayTakeCount'))]\",\n                                                \"subnet\": {\n                                                    \"id\": \"[parameters('subnetIdWeb')]\"\n                                                }\n                                            }\n                                        }\n                                    ],\n                                    \"primary\": true,\n                                    \"enableAcceleratedNetworking\": \"[parameters('moodleCommon').enableAccelNwForOtherVmsSwitch]\",\n                                    \"networkSecurityGroup\": {\n                                        \"id\": \"[ variables('vmssNsgNameId') ]\"\n                                    }\n                                }\n                            }\n                        ]\n                    },\n                    \"osProfile\": {\n                        \"adminUsername\": \"[parameters('moodleCommon').sshUsername]\",\n                        \"computerNamePrefix\": \"[parameters('moodleCommon').vmssName]\",\n                        \"customData\": \"[base64(concat('#cloud-config\\nwrite_files:\\n- encoding: b64\\n  content: ', base64(string(parameters('vmSetupParamsObj'))), '\\n  owner: root:root\\n  path: ', parameters('moodleCommon').moodleOnAzureConfigsJsonPath, '\\n  permissions: ', variables('singleQuote'), '0400', variables('singleQuote')))]\",\n                        \"linuxConfiguration\": {\n                            \"disablePasswordAuthentication\": true,\n                            \"ssh\": {\n                                \"publicKeys\": [\n                                    {\n                                        \"path\": \"[concat('/home/', parameters('moodleCommon').sshUsername, '/.ssh/authorized_keys')]\",\n                                        \"keyData\": \"[parameters('moodleCommon').sshPublicKey]\"\n                                    }\n                                ]\n                            }\n                        }\n                    },\n                    \"storageProfile\": {\n                        \"imageReference\": \"[parameters('moodleCommon').osType]\",\n                        \"osDisk\": {\n                            \"caching\": \"ReadOnly\",\n                            \"createOption\": \"FromImage\",\n                            \"diskSizeGB\": \"[parameters('moodleCommon').OSDiskSizeInGB]\",\n                            \"managedDisk\": {\n                                \"storageAccountType\": \"[parameters('moodleCommon').osDiskStorageType]\"\n                            }\n                        }\n                    }\n                }\n            },\n            \"sku\": {\n                \"capacity\": 1,\n                \"name\": \"[parameters('moodleCommon').autoscaleVmSku]\",\n                \"tier\": \"Standard\"\n            },\n            \"tags\": {\n                \"displayName\": \"webfarm\"\n            }\n        },\n        {\n            \"type\": \"Microsoft.Insights/autoscaleSettings\",\n            \"apiVersion\": \"2015-04-01\",\n            \"dependsOn\": [\n                \"[concat('Microsoft.Compute/virtualMachineScaleSets/', parameters('moodleCommon').vmssName)]\"\n            ],\n            \"location\": \"[parameters('moodleCommon').location]\",\n            \"name\": \"autoscalewad\",\n            \"properties\": {\n                \"enabled\": true,\n                \"name\": \"autoscalewad\",\n                \"profiles\": [\n                    {\n                        \"capacity\": {\n                            \"default\": \"[parameters('moodleCommon').autoscaleVmCountMin]\",\n                            \"maximum\": \"[parameters('moodleCommon').autoscaleVmCountMax]\",\n                            \"minimum\": \"[parameters('moodleCommon').autoscaleVmCountMin]\"\n                        },\n                        \"name\": \"Profile1\",\n                        \"rules\": [\n                            {\n                                \"metricTrigger\": {\n                                    \"metricName\": \"Percentage CPU\",\n                                    \"metricNamespace\": \"\",\n                                    \"metricResourceUri\": \"[variables('vmssID')]\",\n                                    \"operator\": \"GreaterThan\",\n                                    \"statistic\": \"Average\",\n                                    \"threshold\": 25,\n                                    \"timeAggregation\": \"Average\",\n                                    \"timeGrain\": \"PT1M\",\n                                    \"timeWindow\": \"PT5M\"\n                                },\n                                \"scaleAction\": {\n                                    \"cooldown\": \"PT30M\",\n                                    \"direction\": \"Increase\",\n                                    \"type\": \"ChangeCount\",\n                                    \"value\": \"2\"\n                                }\n                            },\n                            {\n                                \"metricTrigger\": {\n                                    \"metricName\": \"Percentage CPU\",\n                                    \"metricNamespace\": \"\",\n                                    \"metricResourceUri\": \"[variables('vmssID')]\",\n                                    \"operator\": \"LessThan\",\n                                    \"statistic\": \"Average\",\n                                    \"threshold\": 30,\n                                    \"timeAggregation\": \"Average\",\n                                    \"timeGrain\": \"PT1M\",\n                                    \"timeWindow\": \"PT5M\"\n                                },\n                                \"scaleAction\": {\n                                    \"cooldown\": \"PT20M\",\n                                    \"direction\": \"Decrease\",\n                                    \"type\": \"ChangeCount\",\n                                    \"value\": \"1\"\n                                }\n                            }\n                        ]\n                    }\n                ],\n                \"targetResourceUri\": \"[variables('vmssID')]\"\n            }\n        }\n    ],\n    \"variables\": {\n        \"singleQuote\": \"'\",\n        \"dstorID\": \"[resourceId('Microsoft.Storage/storageAccounts',parameters('moodleCommon').vmssdStorageAccounttName)]\",\n        \"extBeID\": \"[concat(variables('extLbID'),'/backendAddressPools/',parameters('moodleCommon').extBeName)]\",\n        \"extFeID\": \"[concat(variables('extLbID'),'/frontendIPConfigurations/',parameters('moodleCommon').extFeName)]\",\n        \"extLbID\": \"[resourceId('Microsoft.Network/loadBalancers',parameters('moodleCommon').lbName)]\",\n        \"pipID\": \"[resourceId('Microsoft.Network/publicIPAddresses',parameters('moodleCommon').lbPipName)]\",\n        \"scriptUri\": \"[concat(parameters('moodleCommon').scriptLocation,parameters('moodleCommon').webServerSetupScriptFilename,parameters('moodleCommon').artifactsSasToken)]\",\n        \"vmssID\": \"[resourceId('Microsoft.Compute/virtualMachineScaleSets',parameters('moodleCommon').vmssName)]\",\n        \"webvmss1NIC\": \"[concat('Microsoft.Compute/virtualMachineScaleSets/', parameters('moodleCommon').vmssName, '/virtualMachines/0/networkInterfaces/vmssnic')]\",\n        \"appGwID\": \"[resourceId('Microsoft.Network/applicationGateways', parameters('moodleCommon').appGwName)]\",\n        \"appGwBePoolId\": \"[concat(variables('appGwID'), '/backendAddressPools/', parameters('moodleCommon').appGwBePoolName)]\",\n        \"vmssNsgNameId\": \"[resourceId('Microsoft.Network/networkSecurityGroups', parameters('moodleCommon').vmssNsgName)]\",\n        \"lbBePoolArray\": [\n            {\n                \"id\": \"[variables('extBeID')]\"\n            }\n        ],\n        \"lbBePoolArrayTakeCount\": \"[if(equals(parameters('moodleCommon').httpsTermination, 'AppGw'), 0, 1)]\",\n        \"appGwBePoolArray\": [\n            {\n                \"id\": \"[variables('appGwBePoolId')]\"\n            }\n        ],\n        \"appGwBePoolArrayTakeCount\": \"[if(equals(parameters('moodleCommon').httpsTermination, 'AppGw'), 1, 0)]\"\n    },\n    \"outputs\": {\n        \"webvm1IP\": {\n            \"value\": \"[reference(variables('webvmss1NIC'), '2017-03-30').ipConfigurations[0].properties.privateIPAddress]\",\n            \"type\": \"string\"\n        }\n    }\n}\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"azure-moodle\",\n  \"version\": \"1.0.0\",\n  \"description\": \"A package file for developer depenedencies when testing templates\",\n  \"scripts\": {\n    \"test\": \"grunt test\"\n  },\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/Azure/Moodle.git\"\n  },\n  \"author\": \"Catalyst IT\",\n  \"license\": \"GPL V3\",\n  \"bugs\": {\n    \"url\": \"https://github.com/Azure/Moodle/issues\"\n  },\n  \"homepage\": \"https://github.com/Azure/Moodle#readme\",\n  \"devDependencies\": {\n    \"grunt\": \">=1.3.0\",\n    \"grunt-contrib-jshint\": \"^0.11.3\",\n    \"load-grunt-tasks\": \"^3.3.0\"\n  }\n}\n"
  },
  {
    "path": "scripts/helper_functions.sh",
    "content": "#!/bin/bash\n\n# Common functions definitions\n\nfunction wait_for_process {\n  until [ -z $(/usr/bin/pgrep ${1}) ]; do\n    printf '.'\n    sleep 0.5\n  done\n}\n\nfunction apt_update_noninteractive {\n  export DEBIAN_FRONTEND='noninteractive'\n\n  # waiting for apt to finish before running any other commands\n  wait_for_process apt;\n\n  apt --yes -qq -o=Dpkg::Use-Pty=0 update\n}\n\nfunction apt_install_noninteractive {\n  export DEBIAN_FRONTEND='noninteractive'\n  export NEEDRESTART_MODE='a'\n  export ACCEPT_EULA='Y'\n\n  # waiting for apt to finish before running any other commands\n  wait_for_process apt;\n\n  apt --yes --no-install-recommends -qq -o=Dpkg::Use-Pty=0 -o Dpkg::Options::=\"--force-confdef\" -o Dpkg::Options::=\"--force-confold\" install \"${@}\"\n}\n\nfunction get_setup_params_from_configs_json\n{\n    local configs_json_path=${1}    # E.g., /var/lib/cloud/instance/moodle_on_azure_configs.json\n\n    # Added wget command to download jq.\n    wget https://github.com/jqlang/jq/releases/download/jq-1.6/jq-linux64 -O /usr/bin/jq && chmod +x /usr/bin/jq\n\t\n    # Wait for the cloud-init write-files user data file to be generated (just in case)\n    local wait_time_sec=0\n    while [ ! -f \"$configs_json_path\" ]; do\n        sleep 15\n        let \"wait_time_sec += 15\"\n        if [ \"$wait_time_sec\" -ge \"1800\" ]; then\n            echo \"Error: Cloud-init write-files didn't complete in 30 minutes!\"\n            return 1\n        fi\n    done\n\n    local json=$(cat $configs_json_path)\n    export moodleVersion=$(echo $json | jq -r .moodleProfile.version)\n    export glusterNode=$(echo $json | jq -r .fileServerProfile.glusterVmName)\n    export glusterVolume=$(echo $json | jq -r .fileServerProfile.glusterVolName)\n    export siteFQDN=$(echo $json | jq -r .siteProfile.siteURL)\n    export httpsTermination=$(echo $json | jq -r .siteProfile.httpsTermination)\n    export dbIP=$(echo $json | jq -r .dbServerProfile.fqdn)\n    export moodledbname=$(echo $json | jq -r .moodleProfile.dbName)\n    export moodledbuser=$(echo $json | jq -r .moodleProfile.dbUser)\n    export moodledbpass=$(echo $json | jq -r .moodleProfile.dbPassword)\n    export adminpass=$(echo $json | jq -r .moodleProfile.adminPassword)\n    export dbadminlogin=$(echo $json | jq -r .dbServerProfile.adminLogin)\n    export dbadminloginazure=$(echo $json | jq -r .dbServerProfile.adminLoginAzure)\n    export dbadminpass=$(echo $json | jq -r .dbServerProfile.adminPassword)\n    export storageAccountName=$(echo $json | jq -r .moodleProfile.storageAccountName)\n    export storageAccountKey=$(echo $json | jq -r .moodleProfile.storageAccountKey)\n    export azuremoodledbuser=$(echo $json | jq -r .moodleProfile.dbUserAzure)\n    export redisDns=$(echo $json | jq -r .moodleProfile.redisDns)\n    export redisAuth=$(echo $json | jq -r .moodleProfile.redisKey)\n    export elasticVm1IP=$(echo $json | jq -r .moodleProfile.elasticVm1IP)\n    export installO365pluginsSwitch=$(echo $json | jq -r .moodleProfile.installO365pluginsSwitch)\n    export dbServerType=$(echo $json | jq -r .dbServerProfile.type)\n    export fileServerType=$(echo $json | jq -r .fileServerProfile.type)\n    export mssqlDbServiceObjectiveName=$(echo $json | jq -r .dbServerProfile.mssqlDbServiceObjectiveName)\n    export mssqlDbEdition=$(echo $json | jq -r .dbServerProfile.mssqlDbEdition)\n    export mssqlDbSize=$(echo $json | jq -r .dbServerProfile.mssqlDbSize)\n    export installObjectFsSwitch=$(echo $json | jq -r .moodleProfile.installObjectFsSwitch)\n    export installGdprPluginsSwitch=$(echo $json | jq -r .moodleProfile.installGdprPluginsSwitch)\n    export thumbprintSslCert=$(echo $json | jq -r .siteProfile.thumbprintSslCert)\n    export thumbprintCaCert=$(echo $json | jq -r .siteProfile.thumbprintCaCert)\n    export searchType=$(echo $json | jq -r .moodleProfile.searchType)\n    export azureSearchKey=$(echo $json | jq -r .moodleProfile.azureSearchKey)\n    export azureSearchNameHost=$(echo $json | jq -r .moodleProfile.azureSearchNameHost)\n    export tikaVmIP=$(echo $json | jq -r .moodleProfile.tikaVmIP)\n    export syslogServer=$(echo $json | jq -r .moodleProfile.syslogServer)\n    export webServerType=$(echo $json | jq -r .moodleProfile.webServerType)\n    export htmlLocalCopySwitch=$(echo $json | jq -r .moodleProfile.htmlLocalCopySwitch)\n    export nfsVmName=$(echo $json | jq -r .fileServerProfile.nfsVmName)\n    export nfsHaLbIP=$(echo $json | jq -r .fileServerProfile.nfsHaLbIP)\n    export nfsHaExportPath=$(echo $json | jq -r .fileServerProfile.nfsHaExportPath)\n    export nfsByoIpExportPath=$(echo $json | jq -r .fileServerProfile.nfsByoIpExportPath)\n    export storageAccountType=$(echo $json | jq -r .moodleProfile.storageAccountType)\n    export fileServerDiskSize=$(echo $json | jq -r .fileServerProfile.fileServerDiskSize)\n    export phpVersion=$(echo $json | jq -r .phpProfile.phpVersion)\n    export isMigration=$(echo $json | jq -r .moodleProfile.isMigration)\n}\n\nfunction get_php_version {\n# Returns current PHP version, in the form of x.x, eg 7.0 or 7.2\n    if [ -z \"$_PHPVER\" ]; then\n        _PHPVER=`/usr/bin/php -r \"echo PHP_VERSION;\" | /usr/bin/cut -c 1,2,3`\n    fi\n    echo $_PHPVER\n}\n\nfunction install_php_mssql_driver\n{\n    # Download and build php/mssql driver\n    export AZ_REPO=$(lsb_release -cs)\n    export DEBIAN_FRONTEND='noninteractive'\n    export NEEDRESTART_MODE='a'\n    export ACCEPT_EULA='Y'\n\n    mkdir -p /etc/apt/keyrings\n    curl -sLS https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor > /etc/apt/keyrings/microsoft.gpg && chmod go+r /etc/apt/keyrings/microsoft.gpg\n    \n    echo \"deb [arch=`dpkg --print-architecture` signed-by=/etc/apt/keyrings/microsoft.gpg] https://packages.microsoft.com/ubuntu/22.04/prod $AZ_REPO main\" > /etc/apt/sources.list.d/mssql-release.list\n    \n    wait_for_process apt && \\\n    apt_update_noninteractive && \\\n    apt --yes --no-install-recommends -qq install msodbcsql18 mssql-tools18 unixodbc-dev >> /tmp/apt.log\n    \n    echo 'export PATH=\"$PATH:/opt/mssql-tools18/bin\"' >> ~/.bash_profile\n    echo 'export PATH=\"$PATH:/opt/mssql-tools18/bin\"' >> ~/.bashrc\n    source ~/.bashrc\n\n    #Build mssql driver\n    /usr/bin/pear config-set php_ini `php --ini | grep \"Loaded Configuration\" | sed -e \"s|.*:\\s*||\"` system\n    /usr/bin/pecl install sqlsrv\n    /usr/bin/pecl install pdo_sqlsrv\n    PHPVER=$(get_php_version)\n    echo \"extension=sqlsrv.so\" >> /etc/php/$PHPVER/fpm/php.ini\n    echo \"extension=pdo_sqlsrv.so\" >> /etc/php/$PHPVER/fpm/php.ini\n    echo \"extension=sqlsrv.so\" >> /etc/php/$PHPVER/apache2/php.ini\n    echo \"extension=pdo_sqlsrv.so\" >> /etc/php/$PHPVER/apache2/php.ini\n    echo \"extension=sqlsrv.so\" >> /etc/php/$PHPVER/cli/php.ini\n    echo \"extension=pdo_sqlsrv.so\" >> /etc/php/$PHPVER/cli/php.ini\n}\n\nfunction check_fileServerType_param\n{\n    local fileServerType=$1\n    if [ \"$fileServerType\" != \"gluster\" -a \"$fileServerType\" != \"azurefiles\" -a \"$fileServerType\" != \"nfs\" -a \"$fileServerType\" != \"nfs-ha\" -a \"$fileServerType\" != \"nfs-byo\" ]; then\n        echo \"Invalid fileServerType ($fileServerType) given. Only 'gluster', 'azurefiles', 'nfs', 'nfs-ha' or 'nfs-byo' are allowed. Exiting\"\n        exit 1\n    fi\n}\n\nfunction create_azure_files_moodle_share\n{\n    local storageAccountName=$1\n    local storageAccountKey=$2\n    local logFilePath=$3\n    local fileServerDiskSize=$4\n\n    az storage share create \\\n        --name moodle \\\n        --account-name $storageAccountName \\\n        --account-key $storageAccountKey \\\n        --fail-on-exist >>$logFilePath \\\n        --quota $fileServerDiskSize\n}\n\nfunction replace_config_setting_value\n{\n    local setting_name=$1\n    local setting_value=$2\n    local delemeter=$3\n    local file_name=$4\n\n    echo \"Replacing $setting_name $delemeter $setting_value in $file_name\"\n\n    sed -i \"s/^\\($setting_name\\s*$delemeter\\s*\\).*\\$/\\1$setting_value/\" $file_name\n}\n\n# This function can replace only single line $CFG setting in moodle/config.php file.\n# Supported config setting format:\n#      $CFG->setting = 'value';\n# Usage:\n#      replace_moodle_config_value \"setting\" \"value\"\nfunction replace_moodle_config_value\n{\n    local formated_setting_name=\"\\$CFG->$1\"\n    local formated_setting_value=\"'$2';\"\n    local delemeter=\"=\"\n    local moodle_config_file=/moodle/html/moodle/config.php\n    \n    replace_config_setting_value $formated_setting_name $formated_setting_value $delemeter $moodle_config_file\n}\n\nfunction check_azure_files_moodle_share_exists\n{\n    local storageAccountName=$1\n    local storageAccountKey=$2\n\n    local azResponse=$(az storage share exists --name moodle --account-name $storageAccountName --account-key $storageAccountKey)\n    if [ $? -ne 0 ];then\n      echo \"Could not check if moodle file share exists in the storage account ($storageAccountName)\"\n      exit 1\n    fi\n\n    echo \"az storage share exists command response:\"\n    echo $azResponse\n    #Sample 'az storage share exists' command response\n    # { \"exists\": true }\n    local exists=$(echo $azResponse | jq -r .exists)\n\n    if [ \"$exists\" != \"true\" ];then\n      echo \"File share 'moodle' does not exists in the storage account ($storageAccountName)\"\n      exit 1\n    fi\n}\n\nfunction setup_and_mount_gluster_moodle_share\n{\n    local glusterNode=$1\n    local glusterVolume=$2\n\n    grep -q \"/moodle.*glusterfs\" /etc/fstab || echo -e $glusterNode':/'$glusterVolume'   /moodle         glusterfs       defaults,_netdev,log-level=WARNING,log-file=/var/log/gluster.log 0 0' >> /etc/fstab\n    mount /moodle\n}\n\nfunction setup_and_mount_azure_files_moodle_share\n{\n    local storageAccountName=$1\n    local storageAccountKey=$2\n\n    cat <<EOF > /etc/moodle_azure_files.credential\nusername=$storageAccountName\npassword=$storageAccountKey\nEOF\n    chmod 600 /etc/moodle_azure_files.credential\n    \n    grep -q -s \"^//$storageAccountName.file.core.windows.net/moodle\\s\\s*/moodle\\s\\s*cifs\" /etc/fstab && _RET=$? || _RET=$?\n    if [ $_RET != \"0\" ]; then\n        echo -e \"\\n//$storageAccountName.file.core.windows.net/moodle   /moodle cifs    credentials=/etc/moodle_azure_files.credential,uid=www-data,gid=www-data,nofail,vers=3.0,dir_mode=0770,file_mode=0660,serverino,mfsymlinks\" >> /etc/fstab\n    fi\n    mkdir -p /moodle\n    mount /moodle\n}\n\nfunction setup_moodle_mount_dependency_for_systemd_service\n{\n  local serviceName=$1 # E.g., nginx, apache2\n  if [ -z \"$serviceName\" ]; then\n    return 1\n  fi\n\n  local systemdSvcOverrideFileDir=\"/etc/systemd/system/${serviceName}.service.d\"\n  local systemdSvcOverrideFilePath=\"${systemdSvcOverrideFileDir}/moodle_on_azure_override.conf\"\n\n  grep -q -s \"After=moodle.mount\" $systemdSvcOverrideFilePath && _RET=$? || _RET=$?\n  if [ $_RET != \"0\" ]; then\n    mkdir -p $systemdSvcOverrideFileDir\n    cat <<EOF > $systemdSvcOverrideFilePath\n[Unit]\nAfter=moodle.mount\n\n[Service]\nLimitNOFILE=100000\nEOF\n    systemctl daemon-reload\n  fi\n}\n\n# Functions for making NFS share available\n# TODO refactor these functions with the same ones in install_gluster.sh\nfunction scan_for_new_disks\n{\n    local ALLOWLIST=${1}    # E.g., /dev/sda|/dev/sdb\n    declare -a RET\n    local DEVS=$(ls -1 /dev/disk/azure/scsi1/lun*|egrep \"${ALLOWLIST}\"|egrep \"[0-9]$\")\n    for DEV in ${DEVS};\n    do\n        # Check each device if there is a \"1\" partition.  If not,\n        # \"assume\" it is not partitioned.\n        if [ ! -b ${DEV}1 ];\n        then\n            RET+=\"${DEV} \"\n        fi\n    done\n    echo \"${RET}\"\n}\n\nfunction create_raid0_ubuntu {\n    local RAIDDISK=${1}       # E.g., /dev/md1\n    local RAIDCHUNKSIZE=${2}  # E.g., 128\n    local DISKCOUNT=${3}      # E.g., 4\n    shift\n    shift\n    shift\n    local DISKS=\"$@\"\n\n    dpkg -s mdadm && _RET=$? || _RET=$?\n    if [ $_RET -eq 1 ];\n    then \n        echo \"installing mdadm\"\n        apt_install_noninteractive mdadm\n    fi\n    echo \"Creating raid0\"\n    udevadm control --stop-exec-queue\n    echo \"yes\" | mdadm --create $RAIDDISK --name=data --level=0 --chunk=$RAIDCHUNKSIZE --raid-devices=$DISKCOUNT $DISKS\n    udevadm control --start-exec-queue\n    mdadm --detail --verbose --scan > /etc/mdadm/mdadm.conf\n}\n\nfunction do_partition {\n    # This function creates one (1) primary partition on the\n    # disk device, using all available space\n    local DISK=${1}   # E.g., /dev/sdc\n\n    echo \"Partitioning disk $DISK\"\n    echo -ne \"n\\np\\n1\\n\\n\\nw\\n\" | fdisk \"${DISK}\" \n    #> /dev/null 2>&1\n\n    #\n    # Use the bash-specific $PIPESTATUS to ensure we get the correct exit code\n    # from fdisk and not from echo\n    if [ ${PIPESTATUS[1]} -ne 0 ];\n    then\n        echo \"An error occurred partitioning ${DISK}\" >&2\n        echo \"I cannot continue\" >&2\n        exit 2\n    fi\n}\n\nfunction add_local_filesystem_to_fstab {\n    local UUID=${1}\n    local MOUNTPOINT=${2}   # E.g., /moodle\n\n    grep -q -s \"${UUID}\" /etc/fstab && _RET=$? || _RET=$?\n    if [ $_RET -eq 0 ];\n    then\n        echo \"Not adding ${UUID} to fstab again (it's already there!)\"\n    else\n        LINE=\"\\nUUID=${UUID} ${MOUNTPOINT} ext4 defaults,noatime 0 0\"\n        echo -e \"${LINE}\" >> /etc/fstab\n    fi\n}\n\nfunction setup_raid_disk_and_filesystem {\n    local MOUNTPOINT=${1}     # E.g., /moodle\n    local RAIDDISK=${2}       # E.g., /dev/md1\n    local RAIDPARTITION=${3}  # E.g., /dev/md1p1\n    local CREATE_FILESYSTEM=${4}  # E.g., \"\" (true) or any non-empty string (false)\n\n    local DISKS=$(scan_for_new_disks \"/dev/disk/azure/scsi1/lun0|/dev/disk/azure/scsi1/lun1\")\n    echo \"Disks are ${DISKS}\"\n    declare -i DISKCOUNT\n    local DISKCOUNT=$(echo \"$DISKS\" | wc -w) \n    echo \"Disk count is $DISKCOUNT\"\n    if [ $DISKCOUNT = \"0\" ]; then\n        echo \"No new (unpartitioned) disks available... Returning non-zero...\"\n        return 1\n    fi\n\n    if [ $DISKCOUNT -gt 1 ]; then\n        create_raid0_ubuntu ${RAIDDISK} 128 $DISKCOUNT $DISKS\n        AZMDL_DISK=$RAIDDISK\n        if [ -z \"$CREATE_FILESYSTEM\" ]; then\n          do_partition ${RAIDDISK}\n          local PARTITION=\"${RAIDPARTITION}\"\n        fi\n    else # Just one unpartitioned disk\n        AZMDL_DISK=$DISKS\n        if [ -z \"$CREATE_FILESYSTEM\" ]; then\n          do_partition ${DISKS}\n          local PARTITION=$(fdisk -l ${DISKS}|grep -A 1 Device|tail -n 1|awk '{print $1}')\n        fi\n    fi\n\n    echo \"Disk (RAID if multiple unpartitioned disks, or as is if only one unpartitioned disk) is set up, and env var AZMDL_DISK is set to '$AZMDL_DISK' for later reference\"\n\n    if [ -z \"$CREATE_FILESYSTEM\" ]; then\n      echo \"Creating filesystem on ${PARTITION}.\"\n      mkfs -t ext4 ${PARTITION}\n      mkdir -p \"${MOUNTPOINT}\"\n      local UUID=$(blkid -u filesystem ${PARTITION}|awk -F \"[= ]\" '{print $3}'|tr -d \"\\\"\")\n      add_local_filesystem_to_fstab \"${UUID}\" \"${MOUNTPOINT}\"\n      echo \"Mounting disk ${PARTITION} on ${MOUNTPOINT}\"\n      mount \"${MOUNTPOINT}\"\n    fi\n}\n\nfunction configure_nfs_server_and_export {\n    local MOUNTPOINT=${1}     # E.g., /moodle\n\n    echo \"Installing nfs server...\"\n    apt_install_noninteractive nfs-kernel-server\n\n    echo \"Exporting ${MOUNTPOINT}...\"\n    grep -q -s \"^${MOUNTPOINT}\" /etc/exports && _RET=$? || _RET=$?\n    if [ $_RET = \"0\" ]; then\n        echo \"${MOUNTPOINT} is already exported. Returning...\"\n    else\n        echo -e \"\\n${MOUNTPOINT}   *(rw,sync,no_root_squash)\" >> /etc/exports\n        systemctl restart nfs-kernel-server.service\n    fi\n}\n\nfunction configure_nfs_client_and_mount0 {\n    local NFS_HOST_EXPORT_PATH=${1}   # E.g., controller-vm-ab12cd:/moodle or 172.16.3.100:/drbd/data\n    local MOUNTPOINT=${2}             # E.g., /moodle\n\n    apt install -y nfs-common\n    mkdir -p ${MOUNTPOINT}\n\n    grep -q -s \"^${NFS_HOST_EXPORT_PATH}\" /etc/fstab && _RET=$? || _RET=$?\n    if [ $_RET = \"0\" ]; then\n        echo \"${NFS_HOST_EXPORT_PATH} already in /etc/fstab... skipping to add\"\n    else\n        echo -e \"\\n${NFS_HOST_EXPORT_PATH}    ${MOUNTPOINT}    nfs    auto    0    0\" >> /etc/fstab\n    fi\n    mount ${MOUNTPOINT}\n}\n\nfunction configure_nfs_client_and_mount {\n    local NFS_SERVER=${1}     # E.g., controller-vm-ab12cd or IP (NFS-HA LB)\n    local NFS_DIR=${2}        # E.g., /moodle or /drbd/data\n    local MOUNTPOINT=${3}     # E.g., /moodle\n\n    configure_nfs_client_and_mount0 \"${NFS_SERVER}:${NFS_DIR}\" ${MOUNTPOINT}\n}\n\nSERVER_TIMESTAMP_FULLPATH=\"/moodle/html/moodle/.last_modified_time.moodle_on_azure\"\nLOCAL_TIMESTAMP_FULLPATH=\"/var/www/html/moodle/.last_modified_time.moodle_on_azure\"\n\n# Create a script to sync /moodle/html/moodle (gluster/NFS) and /var/www/html/moodle (local) and set up a minutely cron job\n# Should be called by root and only on a VMSS web frontend VM\nfunction setup_html_local_copy_cron_job {\n  if [ \"$(whoami)\" != \"root\" ]; then\n    echo \"${0}: Must be run as root!\"\n    return 1\n  fi\n\n  local SYNC_SCRIPT_FULLPATH=\"/usr/local/bin/sync_moodle_html_local_copy_if_modified.sh\"\n  mkdir -p $(dirname ${SYNC_SCRIPT_FULLPATH})\n\n  local SYNC_LOG_FULLPATH=\"/var/log/moodle-html-sync.log\"\n\n  cat <<EOF > ${SYNC_SCRIPT_FULLPATH}\n#!/bin/bash\n\nsleep \\$((\\$RANDOM%30))\n\nif [ -f \"$SERVER_TIMESTAMP_FULLPATH\" ]; then\n  SERVER_TIMESTAMP=\\$(cat $SERVER_TIMESTAMP_FULLPATH)\n  if [ -f \"$LOCAL_TIMESTAMP_FULLPATH\" ]; then\n    LOCAL_TIMESTAMP=\\$(cat $LOCAL_TIMESTAMP_FULLPATH)\n  else\n    logger -p local2.notice -t moodle \"Local timestamp file ($LOCAL_TIMESTAMP_FULLPATH) does not exist. Probably first time syncing? Continuing to sync.\"\n    mkdir -p /var/www/html\n  fi\n  if [ \"\\$SERVER_TIMESTAMP\" != \"\\$LOCAL_TIMESTAMP\" ]; then\n    logger -p local2.notice -t moodle \"Server time stamp (\\$SERVER_TIMESTAMP) is different from local time stamp (\\$LOCAL_TIMESTAMP). Start syncing...\"\n    if [[ \\$(find $SYNC_LOG_FULLPATH -type f -size +20M 2> /dev/null) ]]; then\n      truncate -s 0 $SYNC_LOG_FULLPATH\n    fi\n    echo \\$(date +%Y%m%d%H%M%S) >> $SYNC_LOG_FULLPATH\n    rsync -av --delete /moodle/html/moodle /var/www/html >> $SYNC_LOG_FULLPATH\n  fi\nelse\n  logger -p local2.notice -t moodle \"Remote timestamp file ($SERVER_TIMESTAMP_FULLPATH) does not exist. Is /moodle mounted? Exiting with error.\"\n  exit 1\nfi\nEOF\n  chmod 500 ${SYNC_SCRIPT_FULLPATH}\n\n  local CRON_DESC_FULLPATH=\"/etc/cron.d/sync-moodle-html-local-copy\"\n  cat <<EOF > ${CRON_DESC_FULLPATH}\n* * * * * root ${SYNC_SCRIPT_FULLPATH}\nEOF\n  chmod 644 ${CRON_DESC_FULLPATH}\n\n  # Addition of a hook for custom script run on VMSS from shared mount to allow customised configuration of the VMSS as required\n  local CRON_DESC_FULLPATH2=\"/etc/cron.d/update-vmss-config\"\n  cat <<EOF > ${CRON_DESC_FULLPATH2}\n* * * * * root [ -f /moodle/bin/update-vmss-config ] && /bin/bash /moodle/bin/update-vmss-config\nEOF\n  chmod 644 ${CRON_DESC_FULLPATH2}\n}\n\nLAST_MODIFIED_TIME_UPDATE_SCRIPT_FULLPATH=\"/usr/local/bin/update_last_modified_time.moodle_on_azure.sh\"\n\n# Create a script to modify the last modified timestamp file (/moodle/html/moodle/last_modified_time.moodle_on_azure)\n# Should be called by root and only on the controller VM.\n# The moodle admin should run the generated script everytime the /moodle/html/moodle directory content is updated (e.g., moodle upgrade, config change or plugin install/upgrade)\nfunction create_last_modified_time_update_script {\n  if [ \"$(whoami)\" != \"root\" ]; then\n    echo \"${0}: Must be run as root!\"\n    return 1\n  fi\n\n  mkdir -p $(dirname $LAST_MODIFIED_TIME_UPDATE_SCRIPT_FULLPATH)\n  cat <<EOF > $LAST_MODIFIED_TIME_UPDATE_SCRIPT_FULLPATH\n#!/bin/bash\necho \\$(date +%Y%m%d%H%M%S) > $SERVER_TIMESTAMP_FULLPATH\nEOF\n\n  chmod +x $LAST_MODIFIED_TIME_UPDATE_SCRIPT_FULLPATH\n}\n\nfunction run_once_last_modified_time_update_script {\n  $LAST_MODIFIED_TIME_UPDATE_SCRIPT_FULLPATH\n}\n\n# O365 plugins are released only for 'MOODLE_xy_STABLE',\n# whereas we want to support the Moodle tagged versions (e.g., 'v3.4.2').\n# This function helps getting the stable version # (for O365 plugin ver.)\n# from a Moodle version tag. This utility function recognizes tag names\n# of the form 'vx.y.z' only.\nfunction get_o365plugin_version_from_moodle_version {\n  local moodleVersion=${1}\n  if [[ \"$moodleVersion\" =~ v([0-9]+)\\.([0-9]+)\\.([0-9]+) ]]; then\n    echo \"MOODLE_${BASH_REMATCH[1]}${BASH_REMATCH[2]}_STABLE\"\n  else\n    echo $moodleVersion\n  fi\n}\n\n# For Moodle tags (e.g., \"v3.4.2\"), the unzipped Moodle dir is no longer\n# \"moodle-$moodleVersion\", because for tags, it's without \"v\". That is,\n# it's \"moodle-3.4.2\". Therefore, we need a separate helper function for that...\nfunction get_moodle_unzip_dir_from_moodle_version {\n  local moodleVersion=${1}\n  if [[ \"$moodleVersion\" =~ v([0-9]+)\\.([0-9]+)\\.([0-9]+) ]]; then\n    echo \"moodle-${BASH_REMATCH[1]}.${BASH_REMATCH[2]}.${BASH_REMATCH[3]}\"\n  else\n    echo \"moodle-$moodleVersion\"\n  fi\n}\n\n# Long Redis cache Moodle config file generation code moved here\nfunction create_redis_configuration_in_moodledata_muc_config_php\n{\n    # create redis configuration in /moodle/moodledata/muc/config.php\n    cat <<EOF > /moodle/moodledata/muc/config.php\n<?php defined('MOODLE_INTERNAL') || die();\n \\$configuration = array (\n  'siteidentifier' => '7a142be09ea65699e4a6f6ef91c0773c',\n  'stores' => \n  array (\n    'default_application' => \n    array (\n      'name' => 'default_application',\n      'plugin' => 'file',\n      'configuration' => \n      array (\n      ),\n      'features' => 30,\n      'modes' => 3,\n      'default' => true,\n      'class' => 'cachestore_file',\n      'lock' => 'cachelock_file_default',\n    ),\n    'default_session' => \n    array (\n      'name' => 'default_session',\n      'plugin' => 'session',\n      'configuration' => \n      array (\n      ),\n      'features' => 14,\n      'modes' => 2,\n      'default' => true,\n      'class' => 'cachestore_session',\n      'lock' => 'cachelock_file_default',\n    ),\n    'default_request' => \n    array (\n      'name' => 'default_request',\n      'plugin' => 'static',\n      'configuration' => \n      array (\n      ),\n      'features' => 31,\n      'modes' => 4,\n      'default' => true,\n      'class' => 'cachestore_static',\n      'lock' => 'cachelock_file_default',\n    ),\n    'redis' => \n    array (\n      'name' => 'redis',\n      'plugin' => 'redis',\n      'configuration' => \n      array (\n        'server' => '$redisDns',\n        'prefix' => 'moodle_prod',\n        'password' => '$redisAuth',\n        'serializer' => '1',\n      ),\n      'features' => 26,\n      'modes' => 3,\n      'mappingsonly' => false,\n      'class' => 'cachestore_redis',\n      'default' => false,\n      'lock' => 'cachelock_file_default',\n    ),\n    'local_file' => \n    array (\n      'name' => 'local_file',\n      'plugin' => 'file',\n      'configuration' => \n      array (\n        'path' => '/tmp/muc/moodle_prod',\n        'autocreate' => 1,\n      ),\n      'features' => 30,\n      'modes' => 3,\n      'mappingsonly' => false,\n      'class' => 'cachestore_file',\n      'default' => false,\n      'lock' => 'cachelock_file_default',\n    ),\n  ),\n  'modemappings' => \n  array (\n    0 => \n    array (\n      'store' => 'redis',\n      'mode' => 1,\n      'sort' => 0,\n    ),\n    1 => \n    array (\n      'store' => 'default_session',\n      'mode' => 2,\n      'sort' => 0,\n    ),\n    2 => \n    array (\n      'store' => 'default_request',\n      'mode' => 4,\n      'sort' => 0,\n    ),\n  ),\n  'definitions' => \n  array (\n    'core/string' => \n    array (\n      'mode' => 1,\n      'simplekeys' => true,\n      'simpledata' => true,\n      'staticacceleration' => true,\n      'staticaccelerationsize' => 30,\n      'canuselocalstore' => true,\n      'component' => 'core',\n      'area' => 'string',\n      'selectedsharingoption' => 2,\n      'userinputsharingkey' => '',\n      'sharingoptions' => 15,\n    ),\n    'core/langmenu' => \n    array (\n      'mode' => 1,\n      'simplekeys' => true,\n      'simpledata' => true,\n      'staticacceleration' => true,\n      'canuselocalstore' => true,\n      'component' => 'core',\n      'area' => 'langmenu',\n      'selectedsharingoption' => 2,\n      'userinputsharingkey' => '',\n      'sharingoptions' => 15,\n    ),\n    'core/databasemeta' => \n    array (\n      'mode' => 1,\n      'requireidentifiers' => \n      array (\n        0 => 'dbfamily',\n      ),\n      'simpledata' => true,\n      'staticacceleration' => true,\n      'staticaccelerationsize' => 15,\n      'component' => 'core',\n      'area' => 'databasemeta',\n      'selectedsharingoption' => 2,\n      'userinputsharingkey' => '',\n      'sharingoptions' => 15,\n    ),\n    'core/eventinvalidation' => \n    array (\n      'mode' => 1,\n      'staticacceleration' => true,\n      'requiredataguarantee' => true,\n      'simpledata' => true,\n      'component' => 'core',\n      'area' => 'eventinvalidation',\n      'selectedsharingoption' => 2,\n      'userinputsharingkey' => '',\n      'sharingoptions' => 15,\n    ),\n    'core/questiondata' => \n    array (\n      'mode' => 1,\n      'simplekeys' => true,\n      'requiredataguarantee' => false,\n      'datasource' => 'question_finder',\n      'datasourcefile' => 'question/engine/bank.php',\n      'component' => 'core',\n      'area' => 'questiondata',\n      'selectedsharingoption' => 2,\n      'userinputsharingkey' => '',\n      'sharingoptions' => 15,\n    ),\n    'core/htmlpurifier' => \n    array (\n      'mode' => 1,\n      'canuselocalstore' => true,\n      'component' => 'core',\n      'area' => 'htmlpurifier',\n      'selectedsharingoption' => 2,\n      'userinputsharingkey' => '',\n      'sharingoptions' => 15,\n    ),\n    'core/config' => \n    array (\n      'mode' => 1,\n      'staticacceleration' => true,\n      'simpledata' => true,\n      'component' => 'core',\n      'area' => 'config',\n      'selectedsharingoption' => 2,\n      'userinputsharingkey' => '',\n      'sharingoptions' => 15,\n    ),\n    'core/groupdata' => \n    array (\n      'mode' => 1,\n      'simplekeys' => true,\n      'simpledata' => true,\n      'staticacceleration' => true,\n      'staticaccelerationsize' => 2,\n      'component' => 'core',\n      'area' => 'groupdata',\n      'selectedsharingoption' => 2,\n      'userinputsharingkey' => '',\n      'sharingoptions' => 15,\n    ),\n    'core/calendar_subscriptions' => \n    array (\n      'mode' => 1,\n      'simplekeys' => true,\n      'simpledata' => true,\n      'staticacceleration' => true,\n      'component' => 'core',\n      'area' => 'calendar_subscriptions',\n      'selectedsharingoption' => 2,\n      'userinputsharingkey' => '',\n      'sharingoptions' => 15,\n    ),\n    'core/capabilities' => \n    array (\n      'mode' => 1,\n      'simplekeys' => true,\n      'simpledata' => true,\n      'staticacceleration' => true,\n      'staticaccelerationsize' => 1,\n      'ttl' => 3600,\n      'component' => 'core',\n      'area' => 'capabilities',\n      'selectedsharingoption' => 2,\n      'userinputsharingkey' => '',\n      'sharingoptions' => 15,\n    ),\n    'core/yuimodules' => \n    array (\n      'mode' => 1,\n      'component' => 'core',\n      'area' => 'yuimodules',\n      'selectedsharingoption' => 2,\n      'userinputsharingkey' => '',\n      'sharingoptions' => 15,\n    ),\n    'core/observers' => \n    array (\n      'mode' => 1,\n      'simplekeys' => true,\n      'simpledata' => true,\n      'staticacceleration' => true,\n      'staticaccelerationsize' => 2,\n      'component' => 'core',\n      'area' => 'observers',\n      'selectedsharingoption' => 2,\n      'userinputsharingkey' => '',\n      'sharingoptions' => 15,\n    ),\n    'core/plugin_manager' => \n    array (\n      'mode' => 1,\n      'simplekeys' => true,\n      'simpledata' => true,\n      'component' => 'core',\n      'area' => 'plugin_manager',\n      'selectedsharingoption' => 2,\n      'userinputsharingkey' => '',\n      'sharingoptions' => 15,\n    ),\n    'core/coursecattree' => \n    array (\n      'mode' => 1,\n      'staticacceleration' => true,\n      'invalidationevents' => \n      array (\n        0 => 'changesincoursecat',\n      ),\n      'component' => 'core',\n      'area' => 'coursecattree',\n      'selectedsharingoption' => 2,\n      'userinputsharingkey' => '',\n      'sharingoptions' => 15,\n    ),\n    'core/coursecat' => \n    array (\n      'mode' => 2,\n      'invalidationevents' => \n      array (\n        0 => 'changesincoursecat',\n        1 => 'changesincourse',\n      ),\n      'ttl' => 600,\n      'component' => 'core',\n      'area' => 'coursecat',\n      'selectedsharingoption' => 2,\n      'userinputsharingkey' => '',\n      'sharingoptions' => 2,\n    ),\n    'core/coursecatrecords' => \n    array (\n      'mode' => 4,\n      'simplekeys' => true,\n      'invalidationevents' => \n      array (\n        0 => 'changesincoursecat',\n      ),\n      'component' => 'core',\n      'area' => 'coursecatrecords',\n      'selectedsharingoption' => 2,\n      'userinputsharingkey' => '',\n      'sharingoptions' => 2,\n    ),\n    'core/coursecontacts' => \n    array (\n      'mode' => 1,\n      'staticacceleration' => true,\n      'simplekeys' => true,\n      'ttl' => 3600,\n      'component' => 'core',\n      'area' => 'coursecontacts',\n      'selectedsharingoption' => 2,\n      'userinputsharingkey' => '',\n      'sharingoptions' => 15,\n    ),\n    'core/repositories' => \n    array (\n      'mode' => 4,\n      'component' => 'core',\n      'area' => 'repositories',\n      'selectedsharingoption' => 2,\n      'userinputsharingkey' => '',\n      'sharingoptions' => 2,\n    ),\n    'core/externalbadges' => \n    array (\n      'mode' => 1,\n      'simplekeys' => true,\n      'ttl' => 3600,\n      'component' => 'core',\n      'area' => 'externalbadges',\n      'selectedsharingoption' => 2,\n      'userinputsharingkey' => '',\n      'sharingoptions' => 15,\n    ),\n    'core/coursemodinfo' => \n    array (\n      'mode' => 1,\n      'simplekeys' => true,\n      'canuselocalstore' => true,\n      'component' => 'core',\n      'area' => 'coursemodinfo',\n      'selectedsharingoption' => 2,\n      'userinputsharingkey' => '',\n      'sharingoptions' => 15,\n    ),\n    'core/userselections' => \n    array (\n      'mode' => 2,\n      'simplekeys' => true,\n      'simpledata' => true,\n      'component' => 'core',\n      'area' => 'userselections',\n      'selectedsharingoption' => 2,\n      'userinputsharingkey' => '',\n      'sharingoptions' => 2,\n    ),\n    'core/completion' => \n    array (\n      'mode' => 1,\n      'simplekeys' => true,\n      'simpledata' => true,\n      'ttl' => 3600,\n      'staticacceleration' => true,\n      'staticaccelerationsize' => 2,\n      'component' => 'core',\n      'area' => 'completion',\n      'selectedsharingoption' => 2,\n      'userinputsharingkey' => '',\n      'sharingoptions' => 15,\n    ),\n    'core/coursecompletion' => \n    array (\n      'mode' => 1,\n      'simplekeys' => true,\n      'simpledata' => true,\n      'ttl' => 3600,\n      'staticacceleration' => true,\n      'staticaccelerationsize' => 30,\n      'component' => 'core',\n      'area' => 'coursecompletion',\n      'selectedsharingoption' => 2,\n      'userinputsharingkey' => '',\n      'sharingoptions' => 15,\n    ),\n    'core/navigation_expandcourse' => \n    array (\n      'mode' => 2,\n      'simplekeys' => true,\n      'simpledata' => true,\n      'component' => 'core',\n      'area' => 'navigation_expandcourse',\n      'selectedsharingoption' => 2,\n      'userinputsharingkey' => '',\n      'sharingoptions' => 2,\n    ),\n    'core/suspended_userids' => \n    array (\n      'mode' => 4,\n      'simplekeys' => true,\n      'simpledata' => true,\n      'component' => 'core',\n      'area' => 'suspended_userids',\n      'selectedsharingoption' => 2,\n      'userinputsharingkey' => '',\n      'sharingoptions' => 2,\n    ),\n    'core/roledefs' => \n    array (\n      'mode' => 1,\n      'simplekeys' => true,\n      'simpledata' => true,\n      'staticacceleration' => true,\n      'staticaccelerationsize' => 30,\n      'component' => 'core',\n      'area' => 'roledefs',\n      'selectedsharingoption' => 2,\n      'userinputsharingkey' => '',\n      'sharingoptions' => 15,\n    ),\n    'core/plugin_functions' => \n    array (\n      'mode' => 1,\n      'simplekeys' => true,\n      'simpledata' => true,\n      'staticacceleration' => true,\n      'staticaccelerationsize' => 5,\n      'component' => 'core',\n      'area' => 'plugin_functions',\n      'selectedsharingoption' => 2,\n      'userinputsharingkey' => '',\n      'sharingoptions' => 15,\n    ),\n    'core/tags' => \n    array (\n      'mode' => 4,\n      'simplekeys' => true,\n      'staticacceleration' => true,\n      'component' => 'core',\n      'area' => 'tags',\n      'selectedsharingoption' => 2,\n      'userinputsharingkey' => '',\n      'sharingoptions' => 2,\n    ),\n    'core/grade_categories' => \n    array (\n      'mode' => 2,\n      'simplekeys' => true,\n      'invalidationevents' => \n      array (\n        0 => 'changesingradecategories',\n      ),\n      'component' => 'core',\n      'area' => 'grade_categories',\n      'selectedsharingoption' => 2,\n      'userinputsharingkey' => '',\n      'sharingoptions' => 2,\n    ),\n    'core/temp_tables' => \n    array (\n      'mode' => 4,\n      'simplekeys' => true,\n      'simpledata' => true,\n      'component' => 'core',\n      'area' => 'temp_tables',\n      'selectedsharingoption' => 2,\n      'userinputsharingkey' => '',\n      'sharingoptions' => 2,\n    ),\n    'core/tagindexbuilder' => \n    array (\n      'mode' => 2,\n      'simplekeys' => true,\n      'simplevalues' => true,\n      'staticacceleration' => true,\n      'staticaccelerationsize' => 10,\n      'ttl' => 900,\n      'invalidationevents' => \n      array (\n        0 => 'resettagindexbuilder',\n      ),\n      'component' => 'core',\n      'area' => 'tagindexbuilder',\n      'selectedsharingoption' => 2,\n      'userinputsharingkey' => '',\n      'sharingoptions' => 2,\n    ),\n    'core/contextwithinsights' => \n    array (\n      'mode' => 1,\n      'simplekeys' => true,\n      'simpledata' => true,\n      'staticacceleration' => true,\n      'staticaccelerationsize' => 1,\n      'component' => 'core',\n      'area' => 'contextwithinsights',\n      'selectedsharingoption' => 2,\n      'userinputsharingkey' => '',\n      'sharingoptions' => 15,\n    ),\n    'core/message_processors_enabled' => \n    array (\n      'mode' => 1,\n      'simplekeys' => true,\n      'simpledata' => true,\n      'staticacceleration' => true,\n      'staticaccelerationsize' => 3,\n      'component' => 'core',\n      'area' => 'message_processors_enabled',\n      'selectedsharingoption' => 2,\n      'userinputsharingkey' => '',\n      'sharingoptions' => 15,\n    ),\n    'core/message_time_last_message_between_users' => \n    array (\n      'mode' => 1,\n      'simplekeys' => true,\n      'simplevalues' => true,\n      'datasource' => '\\\\core_message\\\\time_last_message_between_users',\n      'component' => 'core',\n      'area' => 'message_time_last_message_between_users',\n      'selectedsharingoption' => 2,\n      'userinputsharingkey' => '',\n      'sharingoptions' => 15,\n    ),\n    'core/fontawesomeiconmapping' => \n    array (\n      'mode' => 1,\n      'simplekeys' => true,\n      'simpledata' => true,\n      'staticacceleration' => true,\n      'staticaccelerationsize' => 1,\n      'component' => 'core',\n      'area' => 'fontawesomeiconmapping',\n      'selectedsharingoption' => 2,\n      'userinputsharingkey' => '',\n      'sharingoptions' => 15,\n    ),\n    'core/postprocessedcss' => \n    array (\n      'mode' => 1,\n      'simplekeys' => true,\n      'simpledata' => true,\n      'staticacceleration' => false,\n      'component' => 'core',\n      'area' => 'postprocessedcss',\n      'selectedsharingoption' => 2,\n      'userinputsharingkey' => '',\n      'sharingoptions' => 15,\n    ),\n    'core/user_group_groupings' => \n    array (\n      'mode' => 1,\n      'simplekeys' => true,\n      'simpledata' => true,\n      'staticacceleration' => true,\n      'component' => 'core',\n      'area' => 'user_group_groupings',\n      'selectedsharingoption' => 2,\n      'userinputsharingkey' => '',\n      'sharingoptions' => 15,\n    ),\n    'availability_grade/scores' => \n    array (\n      'mode' => 1,\n      'staticacceleration' => true,\n      'staticaccelerationsize' => 2,\n      'ttl' => 3600,\n      'component' => 'availability_grade',\n      'area' => 'scores',\n      'selectedsharingoption' => 2,\n      'userinputsharingkey' => '',\n      'sharingoptions' => 15,\n    ),\n    'availability_grade/items' => \n    array (\n      'mode' => 1,\n      'staticacceleration' => true,\n      'staticaccelerationsize' => 2,\n      'ttl' => 3600,\n      'component' => 'availability_grade',\n      'area' => 'items',\n      'selectedsharingoption' => 2,\n      'userinputsharingkey' => '',\n      'sharingoptions' => 15,\n    ),\n    'mod_glossary/concepts' => \n    array (\n      'mode' => 1,\n      'simplekeys' => true,\n      'simpledata' => false,\n      'staticacceleration' => true,\n      'staticaccelerationsize' => 30,\n      'component' => 'mod_glossary',\n      'area' => 'concepts',\n      'selectedsharingoption' => 2,\n      'userinputsharingkey' => '',\n      'sharingoptions' => 15,\n    ),\n    'repository_googledocs/folder' => \n    array (\n      'mode' => 1,\n      'simplekeys' => false,\n      'simpledata' => true,\n      'staticacceleration' => true,\n      'staticaccelerationsize' => 10,\n      'canuselocalstore' => true,\n      'component' => 'repository_googledocs',\n      'area' => 'folder',\n      'selectedsharingoption' => 2,\n      'userinputsharingkey' => '',\n      'sharingoptions' => 15,\n    ),\n    'repository_onedrive/folder' => \n    array (\n      'mode' => 1,\n      'simplekeys' => false,\n      'simpledata' => true,\n      'staticacceleration' => true,\n      'staticaccelerationsize' => 10,\n      'canuselocalstore' => true,\n      'component' => 'repository_onedrive',\n      'area' => 'folder',\n      'selectedsharingoption' => 2,\n      'userinputsharingkey' => '',\n      'sharingoptions' => 15,\n    ),\n    'repository_skydrive/foldername' => \n    array (\n      'mode' => 2,\n      'component' => 'repository_skydrive',\n      'area' => 'foldername',\n      'selectedsharingoption' => 2,\n      'userinputsharingkey' => '',\n      'sharingoptions' => 2,\n    ),\n    'tool_mobile/plugininfo' => \n    array (\n      'mode' => 1,\n      'simplekeys' => true,\n      'staticacceleration' => true,\n      'staticaccelerationsize' => 1,\n      'component' => 'tool_mobile',\n      'area' => 'plugininfo',\n      'selectedsharingoption' => 2,\n      'userinputsharingkey' => '',\n      'sharingoptions' => 15,\n    ),\n    'tool_monitor/eventsubscriptions' => \n    array (\n      'mode' => 1,\n      'simplekeys' => true,\n      'simpledata' => true,\n      'staticacceleration' => true,\n      'staticaccelerationsize' => 10,\n      'component' => 'tool_monitor',\n      'area' => 'eventsubscriptions',\n      'selectedsharingoption' => 2,\n      'userinputsharingkey' => '',\n      'sharingoptions' => 15,\n    ),\n    'tool_uploadcourse/helper' => \n    array (\n      'mode' => 4,\n      'component' => 'tool_uploadcourse',\n      'area' => 'helper',\n      'selectedsharingoption' => 2,\n      'userinputsharingkey' => '',\n      'sharingoptions' => 2,\n    ),\n    'tool_usertours/tourdata' => \n    array (\n      'mode' => 1,\n      'simplekeys' => true,\n      'simpledata' => true,\n      'staticacceleration' => true,\n      'staticaccelerationsize' => 1,\n      'component' => 'tool_usertours',\n      'area' => 'tourdata',\n      'selectedsharingoption' => 2,\n      'userinputsharingkey' => '',\n      'sharingoptions' => 15,\n    ),\n    'tool_usertours/stepdata' => \n    array (\n      'mode' => 1,\n      'simplekeys' => true,\n      'simpledata' => true,\n      'staticacceleration' => true,\n      'staticaccelerationsize' => 1,\n      'component' => 'tool_usertours',\n      'area' => 'stepdata',\n      'selectedsharingoption' => 2,\n      'userinputsharingkey' => '',\n      'sharingoptions' => 15,\n    ),\n  ),\n  'definitionmappings' => \n  array (\n    0 => \n    array (\n      'store' => 'local_file',\n      'definition' => 'core/coursemodinfo',\n      'sort' => 1,\n    ),\n    1 => \n    array (\n      'store' => 'redis',\n      'definition' => 'core/groupdata',\n      'sort' => 1,\n    ),\n    2 => \n    array (\n      'store' => 'redis',\n      'definition' => 'core/roledefs',\n      'sort' => 1,\n    ),\n    3 => \n    array (\n      'store' => 'redis',\n      'definition' => 'tool_usertours/tourdata',\n      'sort' => 1,\n    ),\n    4 => \n    array (\n      'store' => 'redis',\n      'definition' => 'repository_onedrive/folder',\n      'sort' => 1,\n    ),\n    5 => \n    array (\n      'store' => 'redis',\n      'definition' => 'core/message_processors_enabled',\n      'sort' => 1,\n    ),\n    6 => \n    array (\n      'store' => 'redis',\n      'definition' => 'core/coursecontacts',\n      'sort' => 1,\n    ),\n    7 => \n    array (\n      'store' => 'redis',\n      'definition' => 'repository_googledocs/folder',\n      'sort' => 1,\n    ),\n    8 => \n    array (\n      'store' => 'redis',\n      'definition' => 'core/questiondata',\n      'sort' => 1,\n    ),\n    9 => \n    array (\n      'store' => 'redis',\n      'definition' => 'core/coursecat',\n      'sort' => 1,\n    ),\n    10 => \n    array (\n      'store' => 'redis',\n      'definition' => 'core/databasemeta',\n      'sort' => 1,\n    ),\n    11 => \n    array (\n      'store' => 'redis',\n      'definition' => 'core/eventinvalidation',\n      'sort' => 1,\n    ),\n    12 => \n    array (\n      'store' => 'redis',\n      'definition' => 'core/coursecattree',\n      'sort' => 1,\n    ),\n    13 => \n    array (\n      'store' => 'redis',\n      'definition' => 'core/coursecompletion',\n      'sort' => 1,\n    ),\n    14 => \n    array (\n      'store' => 'redis',\n      'definition' => 'core/user_group_groupings',\n      'sort' => 1,\n    ),\n    15 => \n    array (\n      'store' => 'redis',\n      'definition' => 'core/capabilities',\n      'sort' => 1,\n    ),\n    16 => \n    array (\n      'store' => 'redis',\n      'definition' => 'core/yuimodules',\n      'sort' => 1,\n    ),\n    17 => \n    array (\n      'store' => 'redis',\n      'definition' => 'core/observers',\n      'sort' => 1,\n    ),\n    18 => \n    array (\n      'store' => 'redis',\n      'definition' => 'mod_glossary/concepts',\n      'sort' => 1,\n    ),\n    19 => \n    array (\n      'store' => 'redis',\n      'definition' => 'core/fontawesomeiconmapping',\n      'sort' => 1,\n    ),\n    20 => \n    array (\n      'store' => 'redis',\n      'definition' => 'core/config',\n      'sort' => 1,\n    ),\n    21 => \n    array (\n      'store' => 'redis',\n      'definition' => 'tool_mobile/plugininfo',\n      'sort' => 1,\n    ),\n    22 => \n    array (\n      'store' => 'redis',\n      'definition' => 'core/plugin_functions',\n      'sort' => 1,\n    ),\n    23 => \n    array (\n      'store' => 'redis',\n      'definition' => 'core/postprocessedcss',\n      'sort' => 1,\n    ),\n    24 => \n    array (\n      'store' => 'redis',\n      'definition' => 'core/plugin_manager',\n      'sort' => 1,\n    ),\n    25 => \n    array (\n      'store' => 'redis',\n      'definition' => 'tool_usertours/stepdata',\n      'sort' => 1,\n    ),\n    26 => \n    array (\n      'store' => 'redis',\n      'definition' => 'availability_grade/items',\n      'sort' => 1,\n    ),\n    27 => \n    array (\n      'store' => 'local_file',\n      'definition' => 'core/string',\n      'sort' => 1,\n    ),\n    28 => \n    array (\n      'store' => 'redis',\n      'definition' => 'core/externalbadges',\n      'sort' => 1,\n    ),\n    29 => \n    array (\n      'store' => 'local_file',\n      'definition' => 'core/langmenu',\n      'sort' => 1,\n    ),\n    30 => \n    array (\n      'store' => 'local_file',\n      'definition' => 'core/htmlpurifier',\n      'sort' => 1,\n    ),\n    31 => \n    array (\n      'store' => 'redis',\n      'definition' => 'core/completion',\n      'sort' => 1,\n    ),\n    32 => \n    array (\n      'store' => 'redis',\n      'definition' => 'core/calendar_subscriptions',\n      'sort' => 1,\n    ),\n    33 => \n    array (\n      'store' => 'redis',\n      'definition' => 'core/contextwithinsights',\n      'sort' => 1,\n    ),\n    34 => \n    array (\n      'store' => 'redis',\n      'definition' => 'tool_monitor/eventsubscriptions',\n      'sort' => 1,\n    ),\n    35 => \n    array (\n      'store' => 'redis',\n      'definition' => 'core/message_time_last_message_between_users',\n      'sort' => 1,\n    ),\n    36 => \n    array (\n      'store' => 'redis',\n      'definition' => 'availability_grade/scores',\n      'sort' => 1,\n    ),\n  ),\n  'locks' => \n  array (\n    'cachelock_file_default' => \n    array (\n      'name' => 'cachelock_file_default',\n      'type' => 'cachelock_file',\n      'dir' => 'filelocks',\n      'default' => true,\n    ),\n  ),\n);\nEOF\n}\n\n# Long fail2ban config command moved here\nfunction config_fail2ban\n{\n    cat <<EOF > /etc/fail2ban/jail.conf\n# Fail2Ban configuration file.\n#\n# This file was composed for Debian systems from the original one\n# provided now under /usr/share/doc/fail2ban/examples/jail.conf\n# for additional examples.\n#\n# Comments: use '#' for comment lines and ';' for inline comments\n#\n# To avoid merges during upgrades DO NOT MODIFY THIS FILE\n# and rather provide your changes in /etc/fail2ban/jail.local\n#\n\n# The DEFAULT allows a global definition of the options. They can be overridden\n# in each jail afterwards.\n\n[DEFAULT]\n\n# \"ignoreip\" can be an IP address, a CIDR mask or a DNS host. Fail2ban will not\n# ban a host which matches an address in this list. Several addresses can be\n# defined using space separator.\nignoreip = 127.0.0.1/8\n\n# \"bantime\" is the number of seconds that a host is banned.\nbantime  = 600\n\n# A host is banned if it has generated \"maxretry\" during the last \"findtime\"\n# seconds.\nfindtime = 600\nmaxretry = 3\n\n# \"backend\" specifies the backend used to get files modification.\n# Available options are \"pyinotify\", \"gamin\", \"polling\" and \"auto\".\n# This option can be overridden in each jail as well.\n#\n# pyinotify: requires pyinotify (a file alteration monitor) to be installed.\n#            If pyinotify is not installed, Fail2ban will use auto.\n# gamin:     requires Gamin (a file alteration monitor) to be installed.\n#            If Gamin is not installed, Fail2ban will use auto.\n# polling:   uses a polling algorithm which does not require external libraries.\n# auto:      will try to use the following backends, in order:\n#            pyinotify, gamin, polling.\nbackend = auto\n\n# \"usedns\" specifies if jails should trust hostnames in logs,\n#   warn when reverse DNS lookups are performed, or ignore all hostnames in logs\n#\n# yes:   if a hostname is encountered, a reverse DNS lookup will be performed.\n# warn:  if a hostname is encountered, a reverse DNS lookup will be performed,\n#        but it will be logged as a warning.\n# no:    if a hostname is encountered, will not be used for banning,\n#        but it will be logged as info.\nusedns = warn\n\n#\n# Destination email address used solely for the interpolations in\n# jail.{conf,local} configuration files.\ndestemail = root@localhost\n\n#\n# Name of the sender for mta actions\nsendername = Fail2Ban\n\n#\n# ACTIONS\n#\n\n# Default banning action (e.g. iptables, iptables-new,\n# iptables-multiport, shorewall, etc) It is used to define\n# action_* variables. Can be overridden globally or per\n# section within jail.local file\nbanaction = iptables-multiport\n\n# email action. Since 0.8.1 upstream fail2ban uses sendmail\n# MTA for the mailing. Change mta configuration parameter to mail\n# if you want to revert to conventional 'mail'.\nmta = sendmail\n\n# Default protocol\nprotocol = tcp\n\n# Specify chain where jumps would need to be added in iptables-* actions\nchain = INPUT\n\n#\n# Action shortcuts. To be used to define action parameter\n\n# The simplest action to take: ban only\naction_ = %(banaction)s[name=%(__name__)s, port=\"%(port)s\", protocol=\"%(protocol)s\", chain=\"%(chain)s\"]\n\n# ban & send an e-mail with whois report to the destemail.\naction_mw = %(banaction)s[name=%(__name__)s, port=\"%(port)s\", protocol=\"%(protocol)s\", chain=\"%(chain)s\"]\n              %(mta)s-whois[name=%(__name__)s, dest=\"%(destemail)s\", protocol=\"%(protocol)s\", chain=\"%(chain)s\", sendername=\"%(sendername)s\"]\n\n# ban & send an e-mail with whois report and relevant log lines\n# to the destemail.\naction_mwl = %(banaction)s[name=%(__name__)s, port=\"%(port)s\", protocol=\"%(protocol)s\", chain=\"%(chain)s\"]\n               %(mta)s-whois-lines[name=%(__name__)s, dest=\"%(destemail)s\", logpath=%(logpath)s, chain=\"%(chain)s\", sendername=\"%(sendername)s\"]\n\n# Choose default action.  To change, just override value of 'action' with the\n# interpolation to the chosen action shortcut (e.g.  action_mw, action_mwl, etc) in jail.local\n# globally (section [DEFAULT]) or per specific section\naction = %(action_)s\n\n#\n# JAILS\n#\n\n# Next jails corresponds to the standard configuration in Fail2ban 0.6 which\n# was shipped in Debian. Enable any defined here jail by including\n#\n# [SECTION_NAME]\n# enabled = true\n\n#\n# in /etc/fail2ban/jail.local.\n#\n# Optionally you may override any other parameter (e.g. banaction,\n# action, port, logpath, etc) in that section within jail.local\n\n[ssh]\n\nenabled  = true\nport     = ssh\nfilter   = sshd\nlogpath  = /var/log/auth.log\nmaxretry = 6\n\n[dropbear]\n\nenabled  = false\nport     = ssh\nfilter   = dropbear\nlogpath  = /var/log/auth.log\nmaxretry = 6\n\n# Generic filter for pam. Has to be used with action which bans all ports\n# such as iptables-allports, shorewall\n[pam-generic]\n\nenabled  = false\n# pam-generic filter can be customized to monitor specific subset of 'tty's\nfilter   = pam-generic\n# port actually must be irrelevant but lets leave it all for some possible uses\nport     = all\nbanaction = iptables-allports\nport     = anyport\nlogpath  = /var/log/auth.log\nmaxretry = 6\n\n[xinetd-fail]\n\nenabled   = false\nfilter    = xinetd-fail\nport      = all\nbanaction = iptables-multiport-log\nlogpath   = /var/log/daemon.log\nmaxretry  = 2\n\n\n[ssh-ddos]\n\nenabled  = false\nport     = ssh\nfilter   = sshd-ddos\nlogpath  = /var/log/auth.log\nmaxretry = 6\n\n\n# Here we use blackhole routes for not requiring any additional kernel support\n# to store large volumes of banned IPs\n\n[ssh-route]\n\nenabled = false\nfilter = sshd\naction = route\nlogpath = /var/log/sshd.log\nmaxretry = 6\n\n# Here we use a combination of Netfilter/Iptables and IPsets\n# for storing large volumes of banned IPs\n#\n# IPset comes in two versions. See ipset -V for which one to use\n# requires the ipset package and kernel support.\n[ssh-iptables-ipset4]\n\nenabled  = false\nport     = ssh\nfilter   = sshd\nbanaction = iptables-ipset-proto4\nlogpath  = /var/log/sshd.log\nmaxretry = 6\n\n[ssh-iptables-ipset6]\n\nenabled  = false\nport     = ssh\nfilter   = sshd\nbanaction = iptables-ipset-proto6\nlogpath  = /var/log/sshd.log\nmaxretry = 6\n\n\n#\n# HTTP servers\n#\n\n[apache]\n\nenabled  = false\nport     = http,https\nfilter   = apache-auth\nlogpath  = /var/log/apache*/*error.log\nmaxretry = 6\n\n# default action is now multiport, so apache-multiport jail was left\n# for compatibility with previous (<0.7.6-2) releases\n[apache-multiport]\n\nenabled   = false\nport      = http,https\nfilter    = apache-auth\nlogpath   = /var/log/apache*/*error.log\nmaxretry  = 6\n\n[apache-noscript]\n\nenabled  = false\nport     = http,https\nfilter   = apache-noscript\nlogpath  = /var/log/apache*/*error.log\nmaxretry = 6\n\n[apache-overflows]\n\nenabled  = false\nport     = http,https\nfilter   = apache-overflows\nlogpath  = /var/log/apache*/*error.log\nmaxretry = 2\n\n# Ban attackers that try to use PHP's URL-fopen() functionality\n# through GET/POST variables. - Experimental, with more than a year\n# of usage in production environments.\n\n[php-url-fopen]\n\nenabled = false\nport    = http,https\nfilter  = php-url-fopen\nlogpath = /var/www/*/logs/access_log\n\n# A simple PHP-fastcgi jail which works with lighttpd.\n# If you run a lighttpd server, then you probably will\n# find these kinds of messages in your error_log:\n#   ALERT – tried to register forbidden variable ‘GLOBALS’\n#   through GET variables (attacker '1.2.3.4', file '/var/www/default/htdocs/index.php')\n\n[lighttpd-fastcgi]\n\nenabled = false\nport    = http,https\nfilter  = lighttpd-fastcgi\nlogpath = /var/log/lighttpd/error.log\n\n# Same as above for mod_auth\n# It catches wrong authentifications\n\n[lighttpd-auth]\n\nenabled = false\nport    = http,https\nfilter  = suhosin\nlogpath = /var/log/lighttpd/error.log\n\n[nginx-http-auth]\n\nenabled = false\nfilter  = nginx-http-auth\nport    = http,https\nlogpath = /var/log/nginx/error.log\n\n# Monitor roundcube server\n\n[roundcube-auth]\n\nenabled  = false\nfilter   = roundcube-auth\nport     = http,https\nlogpath  = /var/log/roundcube/userlogins\n\n\n[sogo-auth]\n\nenabled  = false\nfilter   = sogo-auth\nport     = http, https\n# without proxy this would be:\n# port    = 20000\nlogpath  = /var/log/sogo/sogo.log\n\n\n#\n# FTP servers\n#\n\n[vsftpd]\n\nenabled  = false\nport     = ftp,ftp-data,ftps,ftps-data\nfilter   = vsftpd\nlogpath  = /var/log/vsftpd.log\n# or overwrite it in jails.local to be\n# logpath = /var/log/auth.log\n# if you want to rely on PAM failed login attempts\n# vsftpd's failregex should match both of those formats\nmaxretry = 6\n\n\n[proftpd]\n\nenabled  = false\nport     = ftp,ftp-data,ftps,ftps-data\nfilter   = proftpd\nlogpath  = /var/log/proftpd/proftpd.log\nmaxretry = 6\n\n\n[pure-ftpd]\n\nenabled  = false\nport     = ftp,ftp-data,ftps,ftps-data\nfilter   = pure-ftpd\nlogpath  = /var/log/syslog\nmaxretry = 6\n\n\n[wuftpd]\n\nenabled  = false\nport     = ftp,ftp-data,ftps,ftps-data\nfilter   = wuftpd\nlogpath  = /var/log/syslog\nmaxretry = 6\n\n\n#\n# Mail servers\n#\n\n[postfix]\n\nenabled  = false\nport     = smtp,ssmtp,submission\nfilter   = postfix\nlogpath  = /var/log/mail.log\n\n\n[couriersmtp]\n\nenabled  = false\nport     = smtp,ssmtp,submission\nfilter   = couriersmtp\nlogpath  = /var/log/mail.log\n\n\n#\n# Mail servers authenticators: might be used for smtp,ftp,imap servers, so\n# all relevant ports get banned\n#\n\n[courierauth]\n\nenabled  = false\nport     = smtp,ssmtp,submission,imap2,imap3,imaps,pop3,pop3s\nfilter   = courierlogin\nlogpath  = /var/log/mail.log\n\n\n[sasl]\n\nenabled  = false\nport     = smtp,ssmtp,submission,imap2,imap3,imaps,pop3,pop3s\nfilter   = postfix-sasl\n# You might consider monitoring /var/log/mail.warn instead if you are\n# running postfix since it would provide the same log lines at the\n# \"warn\" level but overall at the smaller filesize.\nlogpath  = /var/log/mail.log\n\n[dovecot]\n\nenabled = false\nport    = smtp,ssmtp,submission,imap2,imap3,imaps,pop3,pop3s\nfilter  = dovecot\nlogpath = /var/log/mail.log\n\n# To log wrong MySQL access attempts add to /etc/my.cnf:\n# log-error=/var/log/mysqld.log\n# log-warning = 2\n[mysqld-auth]\n\nenabled  = false\nfilter   = mysqld-auth\nport     = 3306\nlogpath  = /var/log/mysqld.log\n\n\n# DNS Servers\n\n\n# These jails block attacks against named (bind9). By default, logging is off\n# with bind9 installation. You will need something like this:\n#\n# logging {\n#     channel security_file {\n#         file \"/var/log/named/security.log\" versions 3 size 30m;\n#         severity dynamic;\n#         print-time yes;\n#     };\n#     category security {\n#         security_file;\n#     };\n# };\n#\n# in your named.conf to provide proper logging\n\n# !!! WARNING !!!\n#   Since UDP is connection-less protocol, spoofing of IP and imitation\n#   of illegal actions is way too simple.  Thus enabling of this filter\n#   might provide an easy way for implementing a DoS against a chosen\n#   victim. See\n#    http://nion.modprobe.de/blog/archives/690-fail2ban-+-dns-fail.html\n#   Please DO NOT USE this jail unless you know what you are doing.\n#[named-refused-udp]\n#\n#enabled  = false\n#port     = domain,953\n#protocol = udp\n#filter   = named-refused\n#logpath  = /var/log/named/security.log\n\n[named-refused-tcp]\n\nenabled  = false\nport     = domain,953\nprotocol = tcp\nfilter   = named-refused\nlogpath  = /var/log/named/security.log\n\n# Multiple jails, 1 per protocol, are necessary ATM:\n# see https://github.com/fail2ban/fail2ban/issues/37\n[asterisk-tcp]\n\nenabled  = false\nfilter   = asterisk\nport     = 5060,5061\nprotocol = tcp\nlogpath  = /var/log/asterisk/messages\n\n[asterisk-udp]\n\nenabled  = false\nfilter\t = asterisk\nport     = 5060,5061\nprotocol = udp\nlogpath  = /var/log/asterisk/messages\n\n\n# Jail for more extended banning of persistent abusers\n# !!! WARNING !!!\n#   Make sure that your loglevel specified in fail2ban.conf/.local\n#   is not at DEBUG level -- which might then cause fail2ban to fall into\n#   an infinite loop constantly feeding itself with non-informative lines\n[recidive]\n\nenabled  = false\nfilter   = recidive\nlogpath  = /var/log/fail2ban.log\naction   = iptables-allports[name=recidive]\n           sendmail-whois-lines[name=recidive, logpath=/var/log/fail2ban.log]\nbantime  = 604800  ; 1 week\nfindtime = 86400   ; 1 day\nmaxretry = 5\nEOF\n}\n"
  },
  {
    "path": "scripts/install_elastic.sh",
    "content": "#!/bin/bash\n# Custom Script for Linux\n\n# The MIT License (MIT)\n#\n# Permission is hereby granted, free of charge, to any person obtaining a copy\n# of this software and associated documentation files (the \"Software\"), to deal\n# in the Software without restriction, including without limitation the rights\n# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n# copies of the Software, and to permit persons to whom the Software is\n# furnished to do so, subject to the following conditions:\n# \n# The above copyright notice and this permission notice shall be included in all\n# copies or substantial portions of the Software.\n# \n# THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n# SOFTWARE.\n\nesClusterName=$1\nelasticvm1ip=$2\nelasticvm2ip=$3\nelasticvm3ip=$4\n\necho $esClusterName    >> /tmp/vars.txt\necho $elasticvm1ip     >> /tmp/vars.txt\necho $elasticvm2ip     >> /tmp/vars.txt\necho $elasticvm3ip     >> /tmp/vars.txt\n\nfunction wait_for_process {\n  until [ -z $(/usr/bin/pgrep ${1}) ]; do\n    printf '.'\n    sleep 0.5\n  done\n}\n\nfunction apt_update_noninteractive {\n  export DEBIAN_FRONTEND='noninteractive'\n\n  # waiting for apt to finish before running any other commands\n  wait_for_process apt;\n\n  apt --yes -qq -o=Dpkg::Use-Pty=0 update\n}\n\nfunction apt_install_noninteractive {\n  export DEBIAN_FRONTEND='noninteractive'\n  export NEEDRESTART_MODE='a'\n  export ACCEPT_EULA='Y'\n\n  # waiting for apt to finish before running any other commands\n  wait_for_process apt;\n\n  apt --yes --no-install-recommends -qq -o=Dpkg::Use-Pty=0 -o Dpkg::Options::=\"--force-confdef\" -o Dpkg::Options::=\"--force-confold\" install \"${@}\"\n}\n\n{\n\n  # make sure the system does automatic update\n  apt_update_noninteractive\n  apt_install_noninteractive unattended-upgrades apt-transport-https\n\n  # configure elastic search repository & install elastic search\n  wget -qO - https://artifacts.elastic.co/GPG-KEY-elasticsearch | gpg --dearmor -o /usr/share/keyrings/elasticsearch-keyring.gpg\n  echo \"deb [signed-by=/usr/share/keyrings/elasticsearch-keyring.gpg] https://artifacts.elastic.co/packages/5.x/apt stable main\" | tee /etc/apt/sources.list.d/elastic-5.x.list\n\n  apt_update_noninteractive\n  apt_install_noninteractive elasticsearch=5.6.16\n\n  # install the required packages\n  apt_install_noninteractive openjdk-8-jre openjdk-8-jdk default-jre default-jdk\n\n  # Configure elasticsearch\n  cat <<EOF > /etc/elasticsearch/elasticsearch.yml\n# ======================== Elasticsearch Configuration =========================\n#\n# NOTE: Elasticsearch comes with reasonable defaults for most settings.\n#       Before you set out to tweak and tune the configuration, make sure you\n#       understand what are you trying to accomplish and the consequences.\n#\n# The primary way of configuring a node is via this file. This template lists\n# the most important settings you may want to configure for a production cluster.\n#\n# Please consult the documentation for further information on configuration options:\n# https://www.elastic.co/guide/en/elasticsearch/reference/index.html\n#\n# ---------------------------------- Cluster -----------------------------------\n#\n# Use a descriptive name for your cluster:\n#\ncluster.name: ${esClusterName}\n#\n# ------------------------------------ Node ------------------------------------\n#\n# Use a descriptive name for the node:\n#\nnode.name: ${HOSTNAME}\n#\n# Add custom attributes to the node:\n#\n#node.attr.rack: r1\n#\n# ----------------------------------- Paths ------------------------------------\n#\n# Path to directory where to store the data (separate multiple locations by comma):\n#\n#path.data: /path/to/data\n#\n# Path to log files:\n#\n#path.logs: /path/to/logs\n#\n# ----------------------------------- Memory -----------------------------------\n#\n# Lock the memory on startup:\n#\n#bootstrap.memory_lock: true\n#\n# Make sure that the heap size is set to about half the memory available\n# on the system and that the owner of the process is allowed to use this\n# limit.\n#\n# Elasticsearch performs poorly when the system is swapping the memory.\n#\n# ---------------------------------- Network -----------------------------------\n#\n# Set the bind address to a specific IP (IPv4 or IPv6):\n#\nnetwork.host: [_eth0_, _local_]\n#\n# Set a custom port for HTTP:\n#\n#http.port: 9200\n#\n# For more information, consult the network module documentation.\n#\n# --------------------------------- Discovery ----------------------------------\n#\n# Pass an initial list of hosts to perform discovery when new node is started:\n# The default list of hosts is [\"127.0.0.1\", \"[::1]\"]\n#\ndiscovery.zen.ping.unicast.hosts: [\"$elasticvm1ip\", \"$elasticvm2ip\", \"$elasticvm3ip\"]\n#\n# Prevent the \"split brain\" by configuring the majority of nodes (total number of master-eligible nodes / 2 + 1):\n#\ndiscovery.zen.minimum_master_nodes: 3\n#\n# For more information, consult the zen discovery module documentation.\n#\n# ---------------------------------- Gateway -----------------------------------\n#\n# Block initial recovery after a full cluster restart until N nodes are started:\n#\n#gateway.recover_after_nodes: 3\n#\n# For more information, consult the gateway module documentation.\n#\n# ---------------------------------- Various -----------------------------------\n#\n# Require explicit names when deleting indices:\n#\n#action.destructive_requires_name: true\nEOF\n\n  service elasticsearch restart\n\n}  > /tmp/setup.log\n"
  },
  {
    "path": "scripts/install_gluster.sh",
    "content": "#!/bin/bash\n\n# This script built for Ubuntu Server 16.04 LTS\n# You can customize variables such as MOUNTPOINT, RAIDCHUNKSIZE and so on to your needs.\n# You can also customize it to work with other Linux flavours and versions.\n# If you customize it, copy it to either Azure blob storage or Github so that Azure\n# custom script Linux VM extension can access it, and specify its location in the \n# parameters of powershell script or runbook or Azure Resource Manager CRP template.\n\n. ./helper_functions.sh\n\nAZUREVMOFFSET=4\n\nNODENAME=$(hostname)\nPEERNODEPREFIX=${1}\nPEERNODEIPPREFIX=${2}\nVOLUMENAME=${3}\nNODEINDEX=${4}\nNODECOUNT=${5}\n\necho $NODENAME          >> /tmp/vars.txt\necho $PEERNODEPREFIX    >> /tmp/vars.txt\necho $PEERNODEIPPREFIX  >> /tmp/vars.txt\necho $VOLUMENAME        >> /tmp/vars.txt\necho $NODEINDEX         >> /tmp/vars.txt\necho $NODECOUNT         >> /tmp/vars.txt\n\n\n\nMOUNTPOINT=\"/datadrive\"\nRAIDCHUNKSIZE=128\n\nRAIDDISK=\"/dev/md1\"\nRAIDPARTITION=\"/dev/md1p1\"\n\n# An set of disks to ignore from partitioning and formatting\nBLACKLIST=\"/dev/sda|/dev/sdb\"\n\n# make sure the system does automatic update\napt_update_noninteractive\napt_install_noninteractive unattended-upgrades\n\n{\n        check_os() {\n            grep -q -s ubuntu /proc/version && _RET=$? || _RET=$?\n            isubuntu=$_RET\n        }\n\n        scan_for_new_disks() {\n            # Looks for unpartitioned disks\n            declare -a RET\n            DEVS=($(ls -1 /dev/sd*|egrep -v \"${BLACKLIST}\"|egrep -v \"[0-9]$\"))\n            for DEV in \"${DEVS[@]}\";\n            do\n                # Check each device if there is a \"1\" partition.  If not,\n                # \"assume\" it is not partitioned.\n                if [ ! -b ${DEV}1 ];\n                then\n                    RET+=\"${DEV} \"\n                fi\n            done\n            echo \"${RET}\"\n        }\n\n        get_disk_count() {\n            DISKCOUNT=0\n            for DISK in \"${DISKS[@]}\";\n            do \n                DISKCOUNT+=1\n            done;\n            echo \"$DISKCOUNT\"\n        }\n\n        create_raid0_ubuntu() {\n            dpkg -s mdadm && _RET=$? || _RET=$?\n            if [ $_RET -eq 1 ];\n            then \n                echo \"installing mdadm\"\n                sudo apt-get -y -q install mdadm\n            fi\n            echo \"Creating raid0\"\n            udevadm control --stop-exec-queue\n            echo \"yes\" | mdadm --create \"$RAIDDISK\" --name=data --level=0 --chunk=\"$RAIDCHUNKSIZE\" --raid-devices=\"$DISKCOUNT\" \"${DISKS[@]}\"\n            udevadm control --start-exec-queue\n            mdadm --detail --verbose --scan > /etc/mdadm.conf\n        }\n\n\n        do_partition() {\n        # This function creates one (1) primary partition on the\n        # disk, using all available space\n            DISK=${1}\n            echo \"Partitioning disk $DISK\"\n            echo -ne \"n\\np\\n1\\n\\n\\nw\\n\" | fdisk \"${DISK}\" \n        #> /dev/null 2>&1\n\n        #\n        # Use the bash-specific $PIPESTATUS to ensure we get the correct exit code\n        # from fdisk and not from echo\n        if [ ${PIPESTATUS[1]} -ne 0 ];\n        then\n            echo \"An error occurred partitioning ${DISK}\" >&2\n            echo \"I cannot continue\" >&2\n            exit 2\n        fi\n        }\n\n        add_to_fstab() {\n            UUID=${1}\n            MOUNTPOINT=${2}\n            grep -q -s \"${UUID}\" /etc/fstab && _RET=$? || _RET=$?\n            if [ $_RET -eq 0 ];\n            then\n                echo \"Not adding ${UUID} to fstab again (it's already there!)\"\n            else\n                LINE=\"UUID=${UUID} ${MOUNTPOINT} ext4 defaults,noatime 0 0\"\n                echo -e \"${LINE}\" >> /etc/fstab\n            fi\n        }\n\n        configure_disks() {\n            ls \"${MOUNTPOINT}\" && _RET=$? || _RET=$?\n            if [ $_RET -eq 0 ]\n            then \n                return\n            fi\n            DISKS=($(scan_for_new_disks))\n            echo \"Disks are ${DISKS[@]}\"\n            declare -i DISKCOUNT\n            DISKCOUNT=$(get_disk_count) \n            echo \"Disk count is $DISKCOUNT\"\n            if [ $DISKCOUNT -gt 1 ];\n            then\n                create_raid0_ubuntu\n                do_partition ${RAIDDISK}\n                PARTITION=\"${RAIDPARTITION}\"\n            else\n                DISK=\"${DISKS[0]}\"\n                do_partition ${DISK}\n                PARTITION=$(fdisk -l ${DISK}|grep -A 1 Device|tail -n 1|awk '{print $1}')\n            fi\n\n            echo \"Creating filesystem on ${PARTITION}.\"\n            mkfs -t ext4 ${PARTITION}\n            mkdir \"${MOUNTPOINT}\"\n            read UUID FS_TYPE < <(blkid -u filesystem ${PARTITION}|awk -F \"[= ]\" '{print $3\" \"$5}'|tr -d \"\\\"\")\n            add_to_fstab \"${UUID}\" \"${MOUNTPOINT}\"\n            echo \"Mounting disk ${PARTITION} on ${MOUNTPOINT}\"\n            mount \"${MOUNTPOINT}\"\n        }\n\n        open_ports() {\n            index=0\n            while [ $index -lt $NODECOUNT ]; do\n\t\t\t    echo \"Node ${index}\"\n\t\t\t\tthisNode=\"${PEERNODEIPPREFIX}.$(($index+$AZUREVMOFFSET))\"\n\t\t\t    echo \"Node ${thisNode}\"\n\n                if [ $index -ne $NODEINDEX ]; then\n\t\t\t\t    echo \"Node ${thisNode} is a peer\"\n                    iptables -I INPUT -p all -s \"${thisNode}\" -j ACCEPT\n                    echo \"${thisNode}    ${thisNode}\" >> /etc/hosts\n                else\n\t\t\t\t    echo \"Node ${thisNode} is me\"\n                    echo \"127.0.0.1    ${thisNode}\" >> /etc/hosts\n                fi\n                let index++\n            done\n            iptables-save\n        }\n\n        disable_apparmor_ubuntu() {\n            /etc/init.d/apparmor teardown\n            update-rc.d -f apparmor remove\n        }\n\n        configure_network() {\n            open_ports\n            disable_apparmor_ubuntu\n        }\n\n        install_glusterfs_ubuntu() {\n            dpkg -l | grep glusterfs && _RET=$? || _RET=$?\n            if [ $_RET -eq 0 ];\n            then\n                return\n            fi\n\n            if [ ! -e /etc/apt/sources.list.d/gluster* ];\n            then\n                echo \"adding gluster ppa\"\n                apt_install_noninteractive python-software-properties\n                apt-add-repository ppa:gluster/glusterfs-3.10 --yes\n                apt_update_noninteractive\n            fi\n            \n            echo \"installing gluster\"\n            apt_install_noninteractive glusterfs-server\n            \n            return\n        }\n\n        configure_gluster() {\n            echo \"gluster step1\"\n\n            if [ $isubuntu -eq 0 ];\n            then\n                /etc/init.d/glusterfs-server status && _RET=$? || _RET=$?\n                if [ $_RET -ne 0 ];\n                then\n                    install_glusterfs_ubuntu\n                fi\n                /etc/init.d/glusterfs-server start\n            fi\n\n\t\t\techo \"gluster step2\"\n            GLUSTERDIR=\"${MOUNTPOINT}/brick\"\n            ls \"${GLUSTERDIR}\" && _RET=$? || _RET=$?\n\n            if [ $_RET -ne 0 ];\n            then\n                mkdir \"${GLUSTERDIR}\"\n            fi\n\n            if [ $NODEINDEX -lt $(($NODECOUNT-1)) ];\n            then\n                return\n            fi\n            \n            echo \"gluster step3\"\n            allNodes=\"${NODENAME}:${GLUSTERDIR}\"\n\t\t\techo $allNodes\n            retry=10\n            failed=1\n\n            while [ $retry -gt 0 ] && [ $failed -gt 0 ]; do\n                failed=0\n                index=0\n                echo retrying $retry \n                while [ $index -lt $(($NODECOUNT-1)) ]; do\n\t\t\t\t\tglustervm=${PEERNODEPREFIX}${index}\n\t\t\t\t\techo $glustervm\n\n                    ping -c 3 $glustervm\n                    gluster peer probe $glustervm && _RET=$? || _RET=$?\n                    if [ $_RET -ne 0 ];\n                    then\n                        failed=1\n                        echo \"gluster peer probe $glustervm failed\"\n                    fi\n\n                    gluster peer status\n                    gluster peer status | grep $glustervm && _RET=$? || _RET=$?\n                    \n                    if [ $_RET -ne 0 ];\n                    then\n                        failed=1\n                        echo \"gluster peer status $glustervm failed\"\n                    fi\n                    \n\t\t\t\t\tif [ $retry -eq 10 ]; then\n                        allNodes=\"${allNodes} $glustervm:${GLUSTERDIR}\"\n                    fi\n                    let index++\n                done\n                sleep 30\n                let retry--\n            done\n\n            echo \"gluster step4\"\n\t\t\techo $allnodes\n            sleep 60\n            gluster volume create ${VOLUMENAME} rep 2 transport tcp ${allNodes} \n            gluster volume info \n            gluster volume start ${VOLUMENAME} \n            echo \"gluster complete\"\n        }\n\n        # \"main routine\"\n        check_os\n        configure_network\n        configure_disks\n        configure_gluster\n\n}  > /tmp/gluster-setup.log\n"
  },
  {
    "path": "scripts/install_moodle.sh",
    "content": "#!/bin/bash\n\n# The MIT License (MIT)\n#\n# Permission is hereby granted, free of charge, to any person obtaining a copy\n# of this software and associated documentation files (the \"Software\"), to deal\n# in the Software without restriction, including without limitation the rights\n# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n# copies of the Software, and to permit persons to whom the Software is\n# furnished to do so, subject to the following conditions:\n# \n# The above copyright notice and this permission notice shall be included in all\n# copies or substantial portions of the Software.\n# \n# THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n# SOFTWARE.\n\nset -ex\n\n#parameters \n{\n    moodle_on_azure_configs_json_path=${1}\n\n    . ./helper_functions.sh\n\n    get_setup_params_from_configs_json $moodle_on_azure_configs_json_path || exit 99\n\n    echo $moodleVersion                 >> /tmp/vars.txt\n    echo $glusterNode                   >> /tmp/vars.txt\n    echo $glusterVolume                 >> /tmp/vars.txt\n    echo $siteFQDN                      >> /tmp/vars.txt\n    echo $httpsTermination              >> /tmp/vars.txt\n    echo $dbIP                          >> /tmp/vars.txt\n    echo $moodledbname                  >> /tmp/vars.txt\n    echo $moodledbuser                  >> /tmp/vars.txt\n    echo $moodledbpass                  >> /tmp/vars.txt\n    echo $adminpass                     >> /tmp/vars.txt\n    echo $dbadminlogin                  >> /tmp/vars.txt\n    echo $dbadminloginazure             >> /tmp/vars.txt\n    echo $dbadminpass                   >> /tmp/vars.txt\n    echo $storageAccountName            >> /tmp/vars.txt\n    echo $storageAccountKey             >> /tmp/vars.txt\n    echo $azuremoodledbuser             >> /tmp/vars.txt\n    echo $redisDns                      >> /tmp/vars.txt\n    echo $redisAuth                     >> /tmp/vars.txt\n    echo $elasticVm1IP                  >> /tmp/vars.txt\n    echo $installO365pluginsSwitch      >> /tmp/vars.txt\n    echo $dbServerType                  >> /tmp/vars.txt\n    echo $fileServerType                >> /tmp/vars.txt\n    echo $mssqlDbServiceObjectiveName   >> /tmp/vars.txt\n    echo $mssqlDbEdition                >> /tmp/vars.txt\n    echo $mssqlDbSize                   >> /tmp/vars.txt\n    echo $installObjectFsSwitch         >> /tmp/vars.txt\n    echo $installGdprPluginsSwitch      >> /tmp/vars.txt\n    echo $thumbprintSslCert             >> /tmp/vars.txt\n    echo $thumbprintCaCert              >> /tmp/vars.txt\n    echo $searchType                    >> /tmp/vars.txt\n    echo $azureSearchKey                >> /tmp/vars.txt\n    echo $azureSearchNameHost           >> /tmp/vars.txt\n    echo $tikaVmIP                      >> /tmp/vars.txt\n    echo $nfsByoIpExportPath            >> /tmp/vars.txt\n    echo $storageAccountType            >>/tmp/vars.txt\n    echo $fileServerDiskSize            >>/tmp/vars.txt\n    echo $phpVersion                    >> /tmp/vars.txt\n    echo $isMigration                   >> /tmp/vars.txt\n\n    check_fileServerType_param $fileServerType\n\n    if [ \"$dbServerType\" = \"mysql\" ]; then\n      mysqlIP=$dbIP\n      mysqladminlogin=$dbadminloginazure\n      mysqladminpass=$dbadminpass\n    elif [ \"$dbServerType\" = \"mssql\" ]; then\n      mssqlIP=$dbIP\n      mssqladminlogin=$dbadminloginazure\n      mssqladminpass=$dbadminpass\n    elif [ \"$dbServerType\" = \"postgres\" ]; then\n      postgresIP=$dbIP\n      pgadminlogin=$dbadminloginazure\n      pgadminpass=$dbadminpass\n    else\n      echo \"Invalid dbServerType ($dbServerType) given. Only 'mysql' or 'postgres' or 'mssql' is allowed. Exiting\"\n      exit 1\n    fi\n\n    #\n    # Export apt default settings for this install script\n    #\n\n    apt_update_noninteractive >> /tmp/apt.log\n    apt_install_noninteractive fail2ban >> /tmp/apt.log\n\n    config_fail2ban\n\n    # create gluster, nfs or Azure Files mount point\n    mkdir -p /moodle\n\n    if [ $fileServerType = \"gluster\" ]; then\n        # configure gluster repository & install gluster clientapt \n        add-apt-repository ppa:gluster/glusterfs-9 --yes >> /tmp/apt.log\n    elif [ $fileServerType = \"nfs\" ]; then\n        # configure NFS server and export\n        setup_raid_disk_and_filesystem /moodle /dev/md1 /dev/md1p1\n        configure_nfs_server_and_export /moodle\n    fi\n\n    apt_update_noninteractive >> /tmp/apt.log\n    apt_install_noninteractive rsyslog git >> /tmp/apt.log\n\n    if [ $fileServerType = \"gluster\" ]; then\n        apt_install_noninteractive glusterfs-client >> /tmp/apt.log\n    elif [ \"$fileServerType\" = \"azurefiles\" ]; then\n        apt_install_noninteractive \\\n            linux-modules-extra-azure \\\n            cifs-utils >> /tmp/apt.log\n\n        # Because of https://bugs.launchpad.net/ubuntu/+source/linux-azure/+bug/2042092\n        modprobe cifs && true\n    fi\n\n    if [ $dbServerType = \"mysql\" ]; then\n        apt_install_noninteractive mysql-client >> /tmp/apt.log\n    elif [ \"$dbServerType\" = \"postgres\" ]; then\n        apt_install_noninteractive postgresql-client >> /tmp/apt.log\n    fi\n\n    if [ \"$installObjectFsSwitch\" = \"true\" -o \"$fileServerType\" = \"azurefiles\" ]; then\n        # install azure cli\n        AZ_REPO=$(lsb_release -cs)\n        mkdir -p /etc/apt/keyrings\n        curl -sLS https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor > /etc/apt/keyrings/microsoft.gpg && chmod go+r /etc/apt/keyrings/microsoft.gpg\n        echo \"deb [arch=`dpkg --print-architecture` signed-by=/etc/apt/keyrings/microsoft.gpg] https://packages.microsoft.com/repos/azure-cli/ $AZ_REPO main\" > /etc/apt/sources.list.d/azure-cli.list\n\n    apt_update_noninteractive >> /tmp/apt.log\n\n    apt_install_noninteractive apt-transport-https ca-certificates curl apt-transport-https lsb-release gnupg azure-cli >> /tmp/apt.log\n\n        # FileStorage accounts can only be used to store Azure file shares;\n        # Premium_LRS will support FileStorage kind\n        # No other storage resources (blob containers, queues, tables, etc.) can be deployed in a FileStorage account.\n        if [ $storageAccountType != \"Premium_LRS\" ]; then\n        az storage container create \\\n            --name objectfs \\\n            --account-name $storageAccountName \\\n            --account-key $storageAccountKey \\\n            --public-access off \\\n            --fail-on-exist >> /tmp/wabs.log\n\n        az storage container policy create \\\n            --account-name $storageAccountName \\\n            --account-key $storageAccountKey \\\n            --container-name objectfs \\\n            --name readwrite \\\n            --start $(date --date=\"1 day ago\" +%F) \\\n            --expiry $(date --date=\"2199-01-01\" +%F) \\\n            --permissions rw >> /tmp/wabs.log\n\n        sas=$(az storage container generate-sas \\\n            --account-name $storageAccountName \\\n            --account-key $storageAccountKey \\\n            --name objectfs \\\n            --policy readwrite \\\n            --output tsv)\n        fi\n\n        # If its a migration flow, then mount the azure file share now.\n        if [ \"$isMigration\" = \"true\" ]; then\n            # On migration flow, the moodle azure file share must present before running this script.\n            echo -e '\\n\\rIts a migration flow, check whether moodle fileshare exists\\n\\r'\n            check_azure_files_moodle_share_exists $storageAccountName $storageAccountKey\n            \n            # Set up and mount Azure Files share.\n            echo -e '\\n\\rSetting up and mounting Azure Files share //'$storageAccountName'.file.core.windows.net/moodle on /moodle\\n\\r'\n            setup_and_mount_azure_files_moodle_share $storageAccountName $storageAccountKey\n        fi\n    fi\n\n    if [ $fileServerType = \"gluster\" ]; then\n        # mount gluster files system\n        echo -e '\\n\\rInstalling GlusterFS on '$glusterNode':/'$glusterVolume '/moodle\\n\\r' \n        setup_and_mount_gluster_moodle_share $glusterNode $glusterVolume\n    elif [ $fileServerType = \"nfs-ha\" ]; then\n        # mount NFS-HA export\n        echo -e '\\n\\rMounting NFS export from '$nfsHaLbIP' on /moodle\\n\\r'\n        configure_nfs_client_and_mount $nfsHaLbIP $nfsHaExportPath /moodle\n    elif [ $fileServerType = \"nfs-byo\" ]; then\n        # mount NFS-BYO export\n        echo -e '\\n\\rMounting NFS export from '$nfsByoIpExportPath' on /moodle\\n\\r'\n        configure_nfs_client_and_mount0 $nfsByoIpExportPath /moodle\n    fi\n\n    # install pre-requisites\n    add-apt-repository ppa:ubuntu-toolchain-r/ppa --yes >> /tmp/apt.log\n    apt_update_noninteractive >> /tmp/apt.log\n    apt_install_noninteractive software-properties-common unzip >> /tmp/apt.log\n\n    # install the entire stack\n    apt_install_noninteractive \\\n        nginx \\\n        varnish \\\n        php \\\n        php-fpm \\\n        php-cli \\\n        php-curl \\\n        php-zip \\\n        graphviz \\\n        aspell \\\n        php-common \\\n        php-soap \\\n        php-json \\\n        php-redis \\\n        php-bcmath \\\n        php-ldap \\\n        php-gd \\\n        php-xmlrpc \\\n        php-intl \\\n        php-xml \\\n        php-bz2 \\\n        php-pear \\\n        php-mbstring \\\n        php-dev \\\n        mcrypt >> /tmp/apt.log\n\n    PhpVer=$(get_php_version)\n    if [ $dbServerType = \"mysql\" ]; then\n        apt_install_noninteractive php-mysql\n    elif [ $dbServerType = \"mssql\" ]; then\n        apt_install_noninteractive libapache2-mod-php\n        install_php_mssql_driver\n    else\n        apt_install_noninteractive php-pgsql\n    fi\n\n    # Set up initial moodle dirs\n    mkdir -p /moodle/html\n    mkdir -p /moodle/certs\n    mkdir -p /moodle/moodledata\n\n    o365pluginVersion=$(get_o365plugin_version_from_moodle_version $moodleVersion)\n    moodleStableVersion=$o365pluginVersion  # Need Moodle stable version for GDPR plugins, and o365pluginVersion is just Moodle stable version, so reuse it.\n    moodleUnzipDir=$(get_moodle_unzip_dir_from_moodle_version $moodleVersion)\n\n    # install Moodle \n    cat <<EOF > /tmp/setup-moodle.sh\n#!/bin/bash\nmkdir -p /moodle/tmp\ncd /moodle/tmp\n\nif [ ! -d /moodle/html/moodle ]; then\n    # downloading moodle only if /moodle/html/moodle does not exist -- if it exists, user should populate it in advance correctly as below. This is to reduce template deployment time.\n    /usr/bin/curl -k --max-redirs 10 https://github.com/moodle/moodle/archive/MOODLE_405_STABLE.zip -L -o moodle.zip\n    /usr/bin/unzip -q moodle.zip\n    /bin/mv \"$moodleUnzipDir\" /moodle/html/moodle\nfi\n\nif [ \"$installGdprPluginsSwitch\" = \"true\" ]; then\n    # install Moodle GDPR plugins (Note: This is only for Moodle versions 3.4.2+ or 3.3.5+ and will be included in Moodle 3.5, so no need for 3.5)\n    curl -k --max-redirs 10 https://github.com/moodlehq/moodle-tool_policy/archive/\"$moodleStableVersion\".zip -L -o plugin-policy.zip\n    unzip -q plugin-policy.zip\n    mv moodle-tool_policy-\"$moodleStableVersion\" /moodle/html/moodle/admin/tool/policy\n\n    curl -k --max-redirs 10 https://github.com/moodlehq/moodle-tool_dataprivacy/archive/\"$moodleStableVersion\".zip -L -o plugin-dataprivacy.zip\n    unzip -q plugin-dataprivacy.zip\n    mv moodle-tool_dataprivacy-\"$moodleStableVersion\" /moodle/html/moodle/admin/tool/dataprivacy\nfi\n\nif [ \"$installO365pluginsSwitch\" = \"true\" ]; then\n    # install Office 365 plugins\n    curl -k --max-redirs 10 https://github.com/Microsoft/o365-moodle/archive/\"$o365pluginVersion\".zip -L -o o365.zip\n    unzip -q o365.zip\n    cp -r o365-moodle-\"$o365pluginVersion\"/* /moodle/html/moodle\n    rm -rf o365-moodle-\"$o365pluginVersion\"\nfi\n\nif [ \"$searchType\" = \"elastic\" ]; then\n    # Install ElasticSearch plugin\n    /usr/bin/curl -k --max-redirs 10 https://github.com/catalyst/moodle-search_elastic/archive/master.zip -L -o plugin-elastic.zip\n    /usr/bin/unzip -q plugin-elastic.zip\n    /bin/mv moodle-search_elastic-master /moodle/html/moodle/search/engine/elastic\n\n    # Install ElasticSearch plugin dependency\n    /usr/bin/curl -k --max-redirs 10 https://github.com/catalyst/moodle-local_aws/archive/master.zip -L -o local-aws.zip\n    /usr/bin/unzip -q local-aws.zip\n    /bin/mv moodle-local_aws-master /moodle/html/moodle/local/aws\n\nelif [ \"$searchType\" = \"azure\" ]; then\n    # Install Azure Search service plugin\n    /usr/bin/curl -k --max-redirs 10 https://github.com/catalyst/moodle-search_azure/archive/master.zip -L -o plugin-azure-search.zip\n    /usr/bin/unzip -q plugin-azure-search.zip\n    /bin/mv moodle-search_azure-master /moodle/html/moodle/search/engine/azure\nfi\n\nif [ \"$installObjectFsSwitch\" = \"true\" ]; then\n    # Install the ObjectFS plugin\n    /usr/bin/curl -k --max-redirs 10 https://github.com/catalyst/moodle-tool_objectfs/archive/master.zip -L -o plugin-objectfs.zip\n    /usr/bin/unzip -q plugin-objectfs.zip\n    /bin/mv moodle-tool_objectfs-master /moodle/html/moodle/admin/tool/objectfs\n\n    # Install the ObjectFS Azure library\n    /usr/bin/curl -k --max-redirs 10 https://github.com/catalyst/moodle-local_azure_storage/archive/master.zip -L -o plugin-azurelibrary.zip\n    /usr/bin/unzip -q plugin-azurelibrary.zip\n    /bin/mv moodle-local_azure_storage-master /moodle/html/moodle/local/azure_storage\nfi\ncd /moodle\nrm -rf /moodle/tmp\nEOF\n\n    chmod 755 /tmp/setup-moodle.sh\n    /tmp/setup-moodle.sh >> /tmp/setupmoodle.log\n\n    # Build nginx config\n    cat <<EOF > /etc/nginx/nginx.conf\nuser www-data;\nworker_processes 2;\npid /run/nginx.pid;\n\nevents {\n    worker_connections 768;\n}\n\nhttp {\n\n  sendfile on;\n  tcp_nopush on;\n  tcp_nodelay on;\n  keepalive_timeout 65;\n  types_hash_max_size 2048;\n  client_max_body_size 0;\n  proxy_max_temp_file_size 0;\n  server_names_hash_bucket_size  128;\n  fastcgi_buffers 16 16k; \n  fastcgi_buffer_size 32k;\n  proxy_buffering off;\n  include /etc/nginx/mime.types;\n  default_type application/octet-stream;\n\n  access_log /var/log/nginx/access.log;\n  error_log /var/log/nginx/error.log;\n\n  set_real_ip_from   127.0.0.1;\n  real_ip_header      X-Forwarded-For;\n  #upgrading to TLSv1.2 and droping 1 & 1.1\n  ssl_protocols TLSv1.2;\n  #ssl_prefer_server_ciphers on;\n  #adding ssl ciphers\n  ssl_ciphers ECDHE-RSA-AES256-GCM-SHA512:DHE-RSA-AES256-GCM-SHA512:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-SHA384;\n\n\n  gzip on;\n  gzip_disable \"msie6\";\n  gzip_vary on;\n  gzip_proxied any;\n  gzip_comp_level 6;\n  gzip_buffers 16 8k;\n  gzip_http_version 1.1;\n  gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript;\nEOF\n\n    if [ \"$httpsTermination\" != \"None\" ]; then\n        cat <<EOF >> /etc/nginx/nginx.conf\n  map \\$http_x_forwarded_proto \\$fastcgi_https {                                                                                          \n    default \\$https;                                                                                                                   \n    http '';                                                                                                                          \n    https on;                                                                                                                         \n  }\nEOF\n    fi\n\n    cat <<EOF >> /etc/nginx/nginx.conf\n  log_format moodle_combined '\\$remote_addr - \\$upstream_http_x_moodleuser [\\$time_local] '\n                             '\"\\$request\" \\$status \\$body_bytes_sent '\n                             '\"\\$http_referer\" \"\\$http_user_agent\"';\n\n\n  include /etc/nginx/conf.d/*.conf;\n  include /etc/nginx/sites-enabled/*;\n}\nEOF\n\n    cat <<EOF >> /etc/nginx/sites-enabled/${siteFQDN}.conf\nserver {\n        listen 81 default;\n        server_name ${siteFQDN};\n        root /moodle/html/moodle;\n        index index.php index.html index.htm;\n\n        # Log to syslog\n        error_log syslog:server=localhost,facility=local1,severity=error,tag=moodle;\n        access_log syslog:server=localhost,facility=local1,severity=notice,tag=moodle moodle_combined;\n\n        # Log XFF IP instead of varnish\n        set_real_ip_from    10.0.0.0/8;\n        set_real_ip_from    127.0.0.1;\n        set_real_ip_from    172.16.0.0/12;\n        set_real_ip_from    192.168.0.0/16;\n        real_ip_header      X-Forwarded-For;\n        real_ip_recursive   on;\nEOF\n    if [ \"$httpsTermination\" != \"None\" ]; then\n        cat <<EOF >> /etc/nginx/sites-enabled/${siteFQDN}.conf\n        # Redirect to https\n        if (\\$http_x_forwarded_proto != https) {\n                return 301 https://\\$server_name\\$request_uri;\n        }\n        rewrite ^/(.*\\.php)(/)(.*)$ /\\$1?file=/\\$3 last;\nEOF\n    fi\n\n    cat <<EOF >> /etc/nginx/sites-enabled/${siteFQDN}.conf\n        # Filter out php-fpm status page\n        location ~ ^/server-status {\n            return 404;\n        }\n\n    location / {\n        try_files \\$uri \\$uri/index.php?\\$query_string;\n    }\n \n    location ~ [^/]\\.php(/|$) {\n        fastcgi_split_path_info ^(.+?\\.php)(/.*)$;\n        if (!-f \\$document_root\\$fastcgi_script_name) {\n                return 404;\n        }\n\n        fastcgi_buffers 16 16k;\n        fastcgi_buffer_size 32k;\n        fastcgi_param   SCRIPT_FILENAME \\$document_root\\$fastcgi_script_name;\n        fastcgi_pass unix:/run/php/php${PhpVer}-fpm.sock;\n        fastcgi_read_timeout 3600;\n        fastcgi_index index.php;\n        include fastcgi_params;\n    }\n}\nEOF\n    if [ \"$httpsTermination\" = \"VMSS\" ]; then\n        cat <<EOF >> /etc/nginx/sites-enabled/${siteFQDN}.conf\nserver {\n        listen 443 ssl;\n        root /moodle/html/moodle;\n        index index.php index.html index.htm;\n\n        ssl on;\n        ssl_certificate /moodle/certs/nginx.crt;\n        ssl_certificate_key /moodle/certs/nginx.key;\n\n        # Log to syslog\n        error_log syslog:server=localhost,facility=local1,severity=error,tag=moodle;\n        access_log syslog:server=localhost,facility=local1,severity=notice,tag=moodle moodle_combined;\n\n        # Log XFF IP instead of varnish\n        set_real_ip_from    10.0.0.0/8;\n        set_real_ip_from    127.0.0.1;\n        set_real_ip_from    172.16.0.0/12;\n        set_real_ip_from    192.168.0.0/16;\n        real_ip_header      X-Forwarded-For;\n        real_ip_recursive   on;\n\n        location / {\n          proxy_set_header Host \\$host;\n          proxy_set_header HTTP_REFERER \\$http_referer;\n          proxy_set_header X-Forwarded-Host \\$host;\n          proxy_set_header X-Forwarded-Server \\$host;\n          proxy_set_header X-Forwarded-Proto https;\n          proxy_set_header X-Forwarded-For \\$proxy_add_x_forwarded_for;\n          proxy_pass http://localhost:80;\n        }\n}\nEOF\n    fi\n\n    if [ \"$httpsTermination\" = \"VMSS\" ]; then\n        ### SSL cert ###\n        if [ \"$thumbprintSslCert\" != \"None\" ]; then\n            echo \"Using VM's cert (/var/lib/waagent/$thumbprintSslCert.*) for SSL...\"\n            cat /var/lib/waagent/$thumbprintSslCert.prv > /moodle/certs/nginx.key\n            cat /var/lib/waagent/$thumbprintSslCert.crt > /moodle/certs/nginx.crt\n            if [ \"$thumbprintCaCert\" != \"None\" ]; then\n                echo \"CA cert was specified (/var/lib/waagent/$thumbprintCaCert.crt), so append it to nginx.crt...\"\n                cat /var/lib/waagent/$thumbprintCaCert.crt >> /moodle/certs/nginx.crt\n            fi\n        else\n            echo -e \"Generating SSL self-signed certificate\"\n            openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout /moodle/certs/nginx.key -out /moodle/certs/nginx.crt -subj \"/C=US/ST=WA/L=Redmond/O=IT/CN=$siteFQDN\"\n        fi\n        chown www-data:www-data /moodle/certs/nginx.*\n        chmod 0400 /moodle/certs/nginx.*\n    fi\n\n   # PHP 8 fpm config\n   PhpVer=$(get_php_version)\n   PhpIni=/etc/php/${PhpVer}/fpm/php.ini\n   sed -i \"s/memory_limit.*/memory_limit = 512M/\" $PhpIni\n   sed -i \"s/max_execution_time.*/max_execution_time = 18000/\" $PhpIni\n   sed -i \"s/;max_input_vars.*/max_input_vars = 100000/\" $PhpIni\n   sed -i \"s/max_input_time.*/max_input_time = 600/\" $PhpIni\n   sed -i \"s/upload_max_filesize.*/upload_max_filesize = 1024M/\" $PhpIni\n   sed -i \"s/post_max_size.*/post_max_size = 1056M/\" $PhpIni\n   sed -i \"s/;opcache.use_cwd.*/opcache.use_cwd = 1/\" $PhpIni\n   sed -i \"s/;opcache.validate_timestamps.*/opcache.validate_timestamps = 1/\" $PhpIni\n   sed -i \"s/;opcache.save_comments.*/opcache.save_comments = 1/\" $PhpIni\n   sed -i \"s/;opcache.enable_file_override.*/opcache.enable_file_override = 0/\" $PhpIni\n   sed -i \"s/;opcache.enable.*/opcache.enable = 1/\" $PhpIni\n   sed -i \"s/;opcache.memory_consumption.*/opcache.memory_consumption = 256/\" $PhpIni\n   sed -i \"s/;opcache.max_accelerated_files.*/opcache.max_accelerated_files = 8000/\" $PhpIni\n\n   # required for PHP8 cli at install time\n   PhpIniCli=/etc/php/${PhpVer}/cli/php.ini\n   sed -i \"s/;max_input_vars.*/max_input_vars = 100000/\" $PhpIniCli\n\n   # fpm config - overload this \n   cat <<EOF > /etc/php/${PhpVer}/fpm/pool.d/www.conf\n[www]\nuser = www-data\ngroup = www-data\nlisten = /run/php/php${PhpVer}-fpm.sock\nlisten.owner = www-data\nlisten.group = www-data\npm = dynamic\npm.max_children = 3000\npm.start_servers = 20 \npm.min_spare_servers = 22 \npm.max_spare_servers = 30 \nEOF\n\n   # Remove the default site. Moodle is the only site we want\n   rm -f /etc/nginx/sites-enabled/default\n\n   # restart Nginx\n   sudo service nginx restart \n\n   # Configure varnish startup for 16.04\n   VARNISHSTART=\"ExecStart=\\/usr\\/sbin\\/varnishd -j unix,user=vcache -F -a :80 -T localhost:6082 -f \\/etc\\/varnish\\/moodle.vcl -S \\/etc\\/varnish\\/secret -s malloc,1024m -p thread_pool_min=200 -p thread_pool_max=4000 -p thread_pool_add_delay=2 -p timeout_linger=100 -p timeout_idle=30 -p send_timeout=1800 -p thread_pools=4 -p http_max_hdr=512 -p workspace_backend=512k\"\n   sed -i \"s/^ExecStart.*/${VARNISHSTART}/\" /lib/systemd/system/varnish.service\n\n   # Configure varnish VCL for moodle\n   cat <<EOF >> /etc/varnish/moodle.vcl\nvcl 4.0;\n\nimport std;\nimport directors;\nbackend default {\n    .host = \"localhost\";\n    .port = \"81\";\n    .first_byte_timeout = 3600s;\n    .connect_timeout = 600s;\n    .between_bytes_timeout = 600s;\n}\n\nsub vcl_recv {\n    # Varnish does not support SPDY or HTTP/2.0 untill we upgrade to Varnish 5.0\n    if (req.method == \"PRI\") {\n        return (synth(405));\n    }\n\n    if (req.restarts == 0) {\n      if (req.http.X-Forwarded-For) {\n        set req.http.X-Forwarded-For = req.http.X-Forwarded-For + \", \" + client.ip;\n      } else {\n        set req.http.X-Forwarded-For = client.ip;\n      }\n    }\n\n    # Non-RFC2616 or CONNECT HTTP requests methods filtered. Pipe requests directly to backend\n    if (req.method != \"GET\" &&\n        req.method != \"HEAD\" &&\n        req.method != \"PUT\" &&\n        req.method != \"POST\" &&\n        req.method != \"TRACE\" &&\n        req.method != \"OPTIONS\" &&\n        req.method != \"DELETE\") {\n      return (pipe);\n    }\n\n    # Varnish don't mess with healthchecks\n    if (req.url ~ \"^/admin/tool/heartbeat\" || req.url ~ \"^/healthcheck.php\")\n    {\n        return (pass);\n    }\n\n    # Pipe requests to backup.php straight to backend - prevents problem with progress bar long polling 503 problem\n    # This is here because backup.php is POSTing to itself - Filter before !GET&&!HEAD\n    if (req.url ~ \"^/backup/backup.php\")\n    {\n        return (pipe);\n    }\n\n    # Varnish only deals with GET and HEAD by default. If request method is not GET or HEAD, pass request to backend\n    if (req.method != \"GET\" && req.method != \"HEAD\") {\n      return (pass);\n    }\n\n    ### Rules for Moodle and Totara sites ###\n    # Moodle doesn't require Cookie to serve following assets. Remove Cookie header from request, so it will be looked up.\n    if ( req.url ~ \"^/altlogin/.+/.+\\.(png|jpg|jpeg|gif|css|js|webp)$\" ||\n         req.url ~ \"^/pix/.+\\.(png|jpg|jpeg|gif)$\" ||\n         req.url ~ \"^/theme/font.php\" ||\n         req.url ~ \"^/theme/image.php\" ||\n         req.url ~ \"^/theme/javascript.php\" ||\n         req.url ~ \"^/theme/jquery.php\" ||\n         req.url ~ \"^/theme/styles.php\" ||\n         req.url ~ \"^/theme/yui\" ||\n         req.url ~ \"^/lib/javascript.php/-1/\" ||\n         req.url ~ \"^/lib/requirejs.php/-1/\"\n        )\n    {\n        set req.http.X-Long-TTL = \"86400\";\n        unset req.http.Cookie;\n        return(hash);\n    }\n\n    # Perform lookup for selected assets that we know are static but Moodle still needs a Cookie\n    if(  req.url ~ \"^/theme/.+\\.(png|jpg|jpeg|gif|css|js|webp)\" ||\n         req.url ~ \"^/lib/.+\\.(png|jpg|jpeg|gif|css|js|webp)\" ||\n         req.url ~ \"^/pluginfile.php/[0-9]+/course/overviewfiles/.+\\.(?i)(png|jpg)$\"\n      )\n    {\n         # Set internal temporary header, based on which we will do things in vcl_backend_response\n         set req.http.X-Long-TTL = \"86400\";\n         return (hash);\n    }\n\n    # Serve requests to SCORM checknet.txt from varnish. Have to remove get parameters. Response body always contains \"1\"\n    if ( req.url ~ \"^/lib/yui/build/moodle-core-checknet/assets/checknet.txt\" )\n    {\n        set req.url = regsub(req.url, \"(.*)\\?.*\", \"\\1\");\n        unset req.http.Cookie; # Will go to hash anyway at the end of vcl_recv\n        set req.http.X-Long-TTL = \"86400\";\n        return(hash);\n    }\n\n    # Requests containing \"Cookie\" or \"Authorization\" headers will not be cached\n    if (req.http.Authorization || req.http.Cookie) {\n        return (pass);\n    }\n\n    # Almost everything in Moodle correctly serves Cache-Control headers, if\n    # needed, which varnish will honor, but there are some which don't. Rather\n    # than explicitly finding them all and listing them here we just fail safe\n    # and don't cache unknown urls that get this far.\n    return (pass);\n}\n\nsub vcl_backend_response {\n    # Happens after we have read the response headers from the backend.\n    # \n    # Here you clean the response headers, removing silly Set-Cookie headers\n    # and other mistakes your backend does.\n\n    # We know these assest are static, let's set TTL >0 and allow client caching\n    if ( beresp.http.Cache-Control && bereq.http.X-Long-TTL && beresp.ttl < std.duration(bereq.http.X-Long-TTL + \"s\", 1s) && !beresp.http.WWW-Authenticate )\n    { # If max-age < defined in X-Long-TTL header\n        set beresp.http.X-Orig-Pragma = beresp.http.Pragma; unset beresp.http.Pragma;\n        set beresp.http.X-Orig-Cache-Control = beresp.http.Cache-Control;\n        set beresp.http.Cache-Control = \"public, max-age=\"+bereq.http.X-Long-TTL+\", no-transform\";\n        set beresp.ttl = std.duration(bereq.http.X-Long-TTL + \"s\", 1s);\n        unset bereq.http.X-Long-TTL;\n    }\n    else if( !beresp.http.Cache-Control && bereq.http.X-Long-TTL && !beresp.http.WWW-Authenticate ) {\n        set beresp.http.X-Orig-Pragma = beresp.http.Pragma; unset beresp.http.Pragma;\n        set beresp.http.Cache-Control = \"public, max-age=\"+bereq.http.X-Long-TTL+\", no-transform\";\n        set beresp.ttl = std.duration(bereq.http.X-Long-TTL + \"s\", 1s);\n        unset bereq.http.X-Long-TTL;\n    }\n    else { # Don't touch headers if max-age > defined in X-Long-TTL header\n        unset bereq.http.X-Long-TTL;\n    }\n\n    # Here we set X-Trace header, prepending it to X-Trace header received from backend. Useful for troubleshooting\n    if(beresp.http.x-trace && !beresp.was_304) {\n        set beresp.http.X-Trace = regsub(server.identity, \"^([^.]+),?.*$\", \"\\1\")+\"->\"+regsub(beresp.backend.name, \"^(.+)\\((?:[0-9]{1,3}\\.){3}([0-9]{1,3})\\)\",\"\\1(\\2)\")+\"->\"+beresp.http.X-Trace;\n    }\n    else {\n        set beresp.http.X-Trace = regsub(server.identity, \"^([^.]+),?.*$\", \"\\1\")+\"->\"+regsub(beresp.backend.name, \"^(.+)\\((?:[0-9]{1,3}\\.){3}([0-9]{1,3})\\)\",\"\\1(\\2)\");\n    }\n\n    # Gzip JS, CSS is done at the ngnix level doing it here dosen't respect the no buffer requsets\n    # if (beresp.http.content-type ~ \"application/javascript.*\" || beresp.http.content-type ~ \"text\") {\n    #    set beresp.do_gzip = true;\n    #}\n}\n\nsub vcl_deliver {\n\n    # Revert back to original Cache-Control header before delivery to client\n    if (resp.http.X-Orig-Cache-Control)\n    {\n        set resp.http.Cache-Control = resp.http.X-Orig-Cache-Control;\n        unset resp.http.X-Orig-Cache-Control;\n    }\n\n    # Revert back to original Pragma header before delivery to client\n    if (resp.http.X-Orig-Pragma)\n    {\n        set resp.http.Pragma = resp.http.X-Orig-Pragma;\n        unset resp.http.X-Orig-Pragma;\n    }\n\n    # (Optional) X-Cache HTTP header will be added to responce, indicating whether object was retrieved from backend, or served from cache\n    if (obj.hits > 0) {\n        set resp.http.X-Cache = \"HIT\";\n    } else {\n        set resp.http.X-Cache = \"MISS\";\n    }\n\n    # Set X-AuthOK header when totara/varnsih authentication succeeded\n    if (req.http.X-AuthOK) {\n        set resp.http.X-AuthOK = req.http.X-AuthOK;\n    }\n\n    # If desired \"Via: 1.1 Varnish-v4\" response header can be removed from response\n    unset resp.http.Via;\n    unset resp.http.Server;\n\n    return(deliver);\n}\n\nsub vcl_backend_error {\n    # More comprehensive varnish error page. Display time, instance hostname, host header, url for easier troubleshooting.\n    set beresp.http.Content-Type = \"text/html; charset=utf-8\";\n    set beresp.http.Retry-After = \"5\";\n    synthetic( {\"\n  <!DOCTYPE html>\n  <html>\n    <head>\n      <title>\"} + beresp.status + \" \" + beresp.reason + {\"</title>\n    </head>\n    <body>\n      <h1>Error \"} + beresp.status + \" \" + beresp.reason + {\"</h1>\n      <p>\"} + beresp.reason + {\"</p>\n      <h3>Guru Meditation:</h3>\n      <p>Time: \"} + now + {\"</p>\n      <p>Node: \"} + server.hostname + {\"</p>\n      <p>Host: \"} + bereq.http.host + {\"</p>\n      <p>URL: \"} + bereq.url + {\"</p>\n      <p>XID: \"} + bereq.xid + {\"</p>\n      <hr>\n      <p>Varnish cache server\n    </body>\n  </html>\n  \"} );\n   return (deliver);\n}\n\nsub vcl_synth {\n\n    #Redirect using '301 - Permanent Redirect', permanent redirect\n    if (resp.status == 851) { \n        set resp.http.Location = req.http.x-redir;\n        set resp.http.X-Varnish-Redirect = true;\n        set resp.status = 301;\n        return (deliver);\n    }\n\n    #Redirect using '302 - Found', temporary redirect\n    if (resp.status == 852) { \n        set resp.http.Location = req.http.x-redir;\n        set resp.http.X-Varnish-Redirect = true;\n        set resp.status = 302;\n        return (deliver);\n    }\n\n    #Redirect using '307 - Temporary Redirect', !GET&&!HEAD requests, dont change method on redirected requests\n    if (resp.status == 857) { \n        set resp.http.Location = req.http.x-redir;\n        set resp.http.X-Varnish-Redirect = true;\n        set resp.status = 307;\n        return (deliver);\n    }\n\n    #Respond with 403 - Forbidden\n    if (resp.status == 863) {\n        set resp.http.X-Varnish-Error = true;\n        set resp.status = 403;\n        return (deliver);\n    }\n}\nEOF\n\n    # Restart Varnish\n    systemctl daemon-reload\n    service varnish restart\n\n    if [ $dbServerType = \"mysql\" ]; then\n        mysql -h $mysqlIP -u $mysqladminlogin -p${mysqladminpass} -e \"CREATE DATABASE ${moodledbname} DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;\" | tee -a /tmp/debug\n        mysql -h $mysqlIP -u $mysqladminlogin -p${mysqladminpass} -e \"CREATE USER ${moodledbuser}@'%' IDENTIFIED BY '${moodledbpass}';\" | tee -a /tmp/debug\n        mysql -h $mysqlIP -u $mysqladminlogin -p${mysqladminpass} -e \"GRANT SELECT,INSERT,UPDATE,DELETE,CREATE,CREATE TEMPORARY TABLES,DROP,INDEX,ALTER ON ${moodledbname}.* TO ${moodledbuser}@'%';\" | tee -a /tmp/debug\n\n    elif [ $dbServerType = \"mssql\" ]; then\n        /opt/mssql-tools/bin/sqlcmd -S $mssqlIP -U $mssqladminlogin -P ${mssqladminpass} -Q \"CREATE DATABASE ${moodledbname} ( MAXSIZE = $mssqlDbSize, EDITION = '$mssqlDbEdition', SERVICE_OBJECTIVE = '$mssqlDbServiceObjectiveName' )\"\n        /opt/mssql-tools/bin/sqlcmd -S $mssqlIP -U $mssqladminlogin -P ${mssqladminpass} -Q \"CREATE LOGIN ${moodledbuser} with password = '${moodledbpass}'\" \n        /opt/mssql-tools/bin/sqlcmd -S $mssqlIP -U $mssqladminlogin -P ${mssqladminpass} -d ${moodledbname} -Q \"CREATE USER ${moodledbuser} FROM LOGIN ${moodledbuser}\"\n        /opt/mssql-tools/bin/sqlcmd -S $mssqlIP -U $mssqladminlogin -P ${mssqladminpass} -d ${moodledbname} -Q \"exec sp_addrolemember 'db_owner','${moodledbuser}'\" \n        \n    else\n        # Create postgres db\n        echo \"${postgresIP}:5432:postgres:${pgadminlogin}:${pgadminpass}\" > /root/.pgpass\n        chmod 600 /root/.pgpass\n        psql -h $postgresIP -U $pgadminlogin -c \"CREATE DATABASE ${moodledbname};\" postgres\n        psql -h $postgresIP -U $pgadminlogin -c \"CREATE USER ${moodledbuser} WITH PASSWORD '${moodledbpass}';\" postgres\n        psql -h $postgresIP -U $pgadminlogin -c \"GRANT ALL ON DATABASE ${moodledbname} TO ${moodledbuser};\" postgres\n        rm -f /root/.pgpass\n    fi\n\n    # Master config for syslog\n    mkdir /var/log/sitelogs\n    chown syslog.adm /var/log/sitelogs\n    cat <<EOF >> /etc/rsyslog.conf\n\\$ModLoad imudp\n\\$UDPServerRun 514\nEOF\n    cat <<EOF >> /etc/rsyslog.d/40-sitelogs.conf\nlocal1.*   /var/log/sitelogs/moodle/access.log\nlocal1.err   /var/log/sitelogs/moodle/error.log\nlocal2.*   /var/log/sitelogs/moodle/cron.log\nEOF\n    service rsyslog restart\n\n    # Fire off moodle setup\n    if [ \"$httpsTermination\" = \"None\" ]; then\n        siteProtocol=\"http\"\n    else\n        siteProtocol=\"https\"\n    fi\n    if [ $dbServerType = \"mysql\" ]; then\n        if [ \"$isMigration\" = \"true\" ]; then\n            echo \"Importing database from the mysql dump file\"\n            if [ ! -f /moodle/migration-db-moodle.sql.tar.gz ]; then\n              echo \"Migrating moodle DB dump archive file not found.\"\n              exit 1\n            fi\n            \n            tar -xvf /moodle/migration-db-moodle.sql.tar.gz -C /moodle/\n            \n            if [ ! -f /moodle/migration-db-moodle.sql ]; then\n              echo \"Migrating moodle DB dump file not found.\"\n              exit 1\n            fi\n            \n            echo \"Importing migration moodle DB.\"\n            mysql -h $mysqlIP -u $mysqladminlogin -p${mysqladminpass} ${moodledbname} < /moodle/migration-db-moodle.sql\n            \n            echo \"Updating moodle db config settings\"\n            replace_moodle_config_value \"dbhost\" \"$mysqlIP\"\n            replace_moodle_config_value \"dbuser\" \"$azuremoodledbuser\"\n            replace_moodle_config_value \"dbpass\" \"$moodledbpass\"\n            \n            echo \"Updating other moodle config settings\"\n            replace_moodle_config_value \"dataroot\" \"\\/moodle\\/moodledata\"\n            replace_moodle_config_value \"wwwroot\" \"$siteProtocol:\\/\\/$siteFQDN\"\n        else\n            echo -e \"cd /tmp; /usr/bin/php /moodle/html/moodle/admin/cli/install.php --chmod=770 --lang=en --wwwroot=\"$siteProtocol\"://\"$siteFQDN\" --dataroot=/moodle/moodledata --dbhost=\"$mysqlIP\" --dbname=\"$moodledbname\" --dbuser=\"$azuremoodledbuser\" --dbpass=\"$moodledbpass\" --dbtype=mysqli --fullname='Moodle LMS' --shortname='Moodle' --adminuser=admin --adminpass=\"$adminpass\" --adminemail=admin@\"$siteFQDN\" --non-interactive --agree-license --allow-unstable || true \"\n            cd /tmp; /usr/bin/php /moodle/html/moodle/admin/cli/install.php --chmod=770 --lang=en --wwwroot=$siteProtocol://$siteFQDN   --dataroot=/moodle/moodledata --dbhost=$mysqlIP   --dbname=$moodledbname   --dbuser=$azuremoodledbuser   --dbpass=$moodledbpass   --dbtype=mysqli --fullname='Moodle LMS' --shortname='Moodle' --adminuser=admin --adminpass=$adminpass   --adminemail=admin@$siteFQDN   --non-interactive --agree-license --allow-unstable || true\n        fi\n\n        if [ \"$installObjectFsSwitch\" = \"true\" ]; then\n            mysql -h $mysqlIP -u $mysqladminlogin -p${mysqladminpass} ${moodledbname} -e \"INSERT INTO mdl_config_plugins (plugin, name, value) VALUES ('tool_objectfs', 'enabletasks', 1);\" \n            mysql -h $mysqlIP -u $mysqladminlogin -p${mysqladminpass} ${moodledbname} -e \"INSERT INTO mdl_config_plugins (plugin, name, value) VALUES ('tool_objectfs', 'filesystem', '\\\\\\tool_objectfs\\\\\\azure_file_system');\"\n            mysql -h $mysqlIP -u $mysqladminlogin -p${mysqladminpass} ${moodledbname} -e \"INSERT INTO mdl_config_plugins (plugin, name, value) VALUES ('tool_objectfs', 'azure_accountname', '${storageAccountName}');\"\n            mysql -h $mysqlIP -u $mysqladminlogin -p${mysqladminpass} ${moodledbname} -e \"INSERT INTO mdl_config_plugins (plugin, name, value) VALUES ('tool_objectfs', 'azure_container', 'objectfs');\"\n            mysql -h $mysqlIP -u $mysqladminlogin -p${mysqladminpass} ${moodledbname} -e \"INSERT INTO mdl_config_plugins (plugin, name, value) VALUES ('tool_objectfs', 'azure_sastoken', '${sas}');\"\n        fi\n    elif [ $dbServerType = \"mssql\" ]; then\n        cd /tmp; /usr/bin/php /moodle/html/moodle/admin/cli/install.php --chmod=770 --lang=en --wwwroot=$siteProtocol://$siteFQDN   --dataroot=/moodle/moodledata --dbhost=$mssqlIP   --dbname=$moodledbname   --dbuser=$azuremoodledbuser   --dbpass=$moodledbpass   --dbtype=sqlsrv --fullname='Moodle LMS' --shortname='Moodle' --adminuser=admin --adminpass=$adminpass   --adminemail=admin@$siteFQDN   --non-interactive --agree-license --allow-unstable || true\n\n        if [ \"$installObjectFsSwitch\" = \"true\" ]; then\n            /opt/mssql-tools/bin/sqlcmd -S $mssqlIP -U $mssqladminlogin -P ${mssqladminpass} -d ${moodledbname} -Q \"INSERT INTO mdl_config_plugins (plugin, name, value) VALUES ('tool_objectfs', 'enabletasks', 1)\" \n            /opt/mssql-tools/bin/sqlcmd -S $mssqlIP -U $mssqladminlogin -P ${mssqladminpass} -d ${moodledbname} -Q \"INSERT INTO mdl_config_plugins (plugin, name, value) VALUES ('tool_objectfs', 'filesystem', '\\\\\\tool_objectfs\\\\\\azure_file_system')\"\n            /opt/mssql-tools/bin/sqlcmd -S $mssqlIP -U $mssqladminlogin -P ${mssqladminpass} -d ${moodledbname} -Q \"INSERT INTO mdl_config_plugins (plugin, name, value) VALUES ('tool_objectfs', 'azure_accountname', '${storageAccountName}')\"\n            /opt/mssql-tools/bin/sqlcmd -S $mssqlIP -U $mssqladminlogin -P ${mssqladminpass} -d ${moodledbname} -Q \"INSERT INTO mdl_config_plugins (plugin, name, value) VALUES ('tool_objectfs', 'azure_container', 'objectfs')\"\n            /opt/mssql-tools/bin/sqlcmd -S $mssqlIP -U $mssqladminlogin -P ${mssqladminpass} -d${moodledbname} -Q \"INSERT INTO mdl_config_plugins (plugin, name, value) VALUES ('tool_objectfs', 'azure_sastoken', '${sas}')\"\n        fi\n    else\n        echo -e \"cd /tmp; /usr/bin/php /moodle/html/moodle/admin/cli/install.php --chmod=770 --lang=en --wwwroot=\"$siteProtocol\"://\"$siteFQDN\" --dataroot=/moodle/moodledata --dbhost=\"$postgresIP\" --dbname=\"$moodledbname\" --dbuser=\"$azuremoodledbuser\" --dbpass=\"$moodledbpass\" --dbtype=pgsql --fullname='Moodle LMS' --shortname='Moodle' --adminuser=admin --adminpass=\"$adminpass\" --adminemail=admin@\"$siteFQDN\" --non-interactive --agree-license --allow-unstable || true \"\n        cd /tmp; /usr/bin/php /moodle/html/moodle/admin/cli/install.php --chmod=770 --lang=en --wwwroot=$siteProtocol://$siteFQDN   --dataroot=/moodle/moodledata --dbhost=$postgresIP   --dbname=$moodledbname   --dbuser=$azuremoodledbuser   --dbpass=$moodledbpass   --dbtype=pgsql --fullname='Moodle LMS' --shortname='Moodle' --adminuser=admin --adminpass=$adminpass   --adminemail=admin@$siteFQDN   --non-interactive --agree-license --allow-unstable || true\n\n        if [ \"$installObjectFsSwitch\" = \"true\" ]; then\n            # Add the ObjectFS configuration to Moodle.\n            echo \"${postgresIP}:5432:${moodledbname}:${azuremoodledbuser}:${moodledbpass}\" > /root/.pgpass\n            chmod 600 /root/.pgpass\n            psql -h $postgresIP -U $azuremoodledbuser -c \"INSERT INTO mdl_config_plugins (plugin, name, value) VALUES ('tool_objectfs', 'enabletasks', 1);\" $moodledbname\n            psql -h $postgresIP -U $azuremoodledbuser -c \"INSERT INTO mdl_config_plugins (plugin, name, value) VALUES ('tool_objectfs', 'filesystem', '\\tool_objectfs\\azure_file_system');\" $moodledbname\n            psql -h $postgresIP -U $azuremoodledbuser -c \"INSERT INTO mdl_config_plugins (plugin, name, value) VALUES ('tool_objectfs', 'azure_accountname', '$storageAccountName');\" $moodledbname\n            psql -h $postgresIP -U $azuremoodledbuser -c \"INSERT INTO mdl_config_plugins (plugin, name, value) VALUES ('tool_objectfs', 'azure_container', 'objectfs');\" $moodledbname\n            psql -h $postgresIP -U $azuremoodledbuser -c \"INSERT INTO mdl_config_plugins (plugin, name, value) VALUES ('tool_objectfs', 'azure_sastoken', '$sas');\" $moodledbname\n        fi\n    fi\n\n    echo -e \"\\n\\rDone! Installation completed!\\n\\r\"\n    \n    # use /tmp/localcachedir/ for localcache and /var/www/html/moodle/ for core_component.php\n    dir=\"/var/www/html/moodle\"\n    if [[ ! -d $dir ]]; then\n        mkdir -p $dir\n    fi\n    sed -i \"22 a \\$CFG->localcachedir = '/tmp/localcachedir';\" /moodle/html/moodle/config.php\n    sed -i \"22 a \\$CFG->alternative_component_cache = '/var/www/html/moodle/core_component.php';\" /moodle/html/moodle/config.php\n    chown -R www-data:www-data $dir\n    chgrp www-data $dir\n    chmod g+s $dir\n    \n    if [ \"$redisAuth\" != \"None\" ]; then\n        create_redis_configuration_in_moodledata_muc_config_php\n\n        # redis configuration in /moodle/html/moodle/config.php\n        sed -i \"23 a \\$CFG->session_redis_lock_expire = 7200;\" /moodle/html/moodle/config.php\n        sed -i \"23 a \\$CFG->session_redis_acquire_lock_timeout = 120;\" /moodle/html/moodle/config.php\n        sed -i \"23 a \\$CFG->session_redis_prefix = 'moodle_prod'; // Optional, default is don't set one.\" /moodle/html/moodle/config.php\n        sed -i \"23 a \\$CFG->session_redis_database = 0;  // Optional, default is db 0.\" /moodle/html/moodle/config.php\n        sed -i \"23 a \\$CFG->session_redis_port = 6379;  // Optional.\" /moodle/html/moodle/config.php\n        sed -i \"23 a \\$CFG->session_redis_host = '$redisDns';\" /moodle/html/moodle/config.php\n        sed -i \"23 a \\$CFG->session_redis_auth = '$redisAuth';\" /moodle/html/moodle/config.php\n        sed -i \"23 a \\$CFG->session_handler_class = '\\\\\\core\\\\\\session\\\\\\redis';\" /moodle/html/moodle/config.php\n    fi\n\n    if [ \"$httpsTermination\" != \"None\" ]; then\n        # We proxy ssl, so moodle needs to know this\n        sed -i \"23 a \\$CFG->sslproxy  = 'true';\" /moodle/html/moodle/config.php\n    fi\n\n    if [ \"$searchType\" = \"elastic\" ]; then\n        # Set up elasticsearch plugin\n        if [ \"$tikaVmIP\" = \"none\" ]; then\n           sed -i \"23 a \\$CFG->forced_plugin_settings = ['search_elastic' => ['hostname' => 'http://$elasticVm1IP']];\" /moodle/html/moodle/config.php\n        else\n           sed -i \"23 a \\$CFG->forced_plugin_settings = ['search_elastic' => ['hostname' => 'http://$elasticVm1IP', 'fileindexing' => 'true', 'tikahostname' => 'http://$tikaVmIP', 'tikaport' => '9998'],];\" /moodle/html/moodle/config.php\n        fi\n\n        sed -i \"23 a \\$CFG->searchengine = 'elastic';\" /moodle/html/moodle/config.php\n        sed -i \"23 a \\$CFG->enableglobalsearch = 'true';\" /moodle/html/moodle/config.php\n        # create index\n        php /moodle/html/moodle/search/cli/indexer.php --force --reindex || true\n\n    elif [ \"$searchType\" = \"azure\" ]; then\n        # Set up Azure Search service plugin\n        if [ \"$tikaVmIP\" = \"none\" ]; then\n           sed -i \"23 a \\$CFG->forced_plugin_settings = ['search_azure' => ['searchurl' => 'https://$azureSearchNameHost', 'apikey' => '$azureSearchKey']];\" /moodle/html/moodle/config.php\n        else\n           sed -i \"23 a \\$CFG->forced_plugin_settings = ['search_azure' => ['searchurl' => 'https://$azureSearchNameHost', 'apikey' => '$azureSearchKey', 'fileindexing' => '1', 'tikahostname' => 'http://$tikaVmIP', 'tikaport' => '9998'],];\" /moodle/html/moodle/config.php\n        fi\n\n        sed -i \"23 a \\$CFG->searchengine = 'azure';\" /moodle/html/moodle/config.php\n        sed -i \"23 a \\$CFG->enableglobalsearch = 'true';\" /moodle/html/moodle/config.php\n        # create index\n        php /moodle/html/moodle/search/cli/indexer.php --force --reindex || true\n\n    fi\n\n    if [ \"$installObjectFsSwitch\" = \"true\" ]; then\n        # Set the ObjectFS alternate filesystem\n        sed -i \"23 a \\$CFG->alternative_file_system_class = '\\\\\\tool_objectfs\\\\\\azure_file_system';\" /moodle/html/moodle/config.php\n    fi\n\n   if [ \"$dbServerType\" = \"postgres\" ]; then\n     # Get a new version of Postgres to match Azure version\n     add-apt-repository \"deb http://apt.postgresql.org/pub/repos/apt/ xenial-pgdg main\"\n     wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | sudo DEBIAN_FRONTEND=noninteractive NEEDRESTART_MODE='a' apt -key add -\n     apt-get update\n     apt-get install -y postgresql-client-9.6\n   fi\n\n   # create cron entry\n   # It is scheduled for once per minute. It can be changed as needed.\n   echo '* * * * * www-data /usr/bin/php /moodle/html/moodle/admin/cli/cron.php 2>&1 | /usr/bin/logger -p local2.notice -t moodle' > /etc/cron.d/moodle-cron\n\n   # Set up cronned sql dump\n   if [ \"$dbServerType\" = \"mysql\" ]; then\n      cat <<EOF > /etc/cron.d/sql-backup\n22 02 * * * root /usr/bin/mysqldump -h $mysqlIP -u ${azuremoodledbuser} -p'${moodledbpass}' --databases ${moodledbname} | gzip > /moodle/db-backup.sql.gz\nEOF\n   elif [ \"$dbServerType\" = \"postgres\" ]; then\n      cat <<EOF > /etc/cron.d/sql-backup\n22 02 * * * root /usr/bin/pg_dump -Fc -h $postgresIP -U ${azuremoodledbuser} ${moodledbname} > /moodle/db-backup.sql\nEOF\n   #else # mssql. TODO It's missed earlier! Complete this!\n   fi\n\n   # Turning off services we don't need the controller running\n   service nginx stop\n   service php${PhpVer}-fpm stop\n   service varnish stop\n   service varnishncsa stop\n   #service varnishlog stop\n\n    # No need to run the commands below any more, as permissions & modes are already as such (no more \"sudo -u www-data ...\")\n    # Leaving this code as a remark that we are explicitly leaving the ownership to root:root\n#    if [ $fileServerType = \"gluster\" -o $fileServerType = \"nfs\" -o $fileServerType = \"nfs-ha\" ]; then\n#       # make sure Moodle can read its code directory but not write\n#       sudo chown -R root.root /moodle/html/moodle\n#       sudo find /moodle/html/moodle -type f -exec chmod 644 '{}' \\;\n#       sudo find /moodle/html/moodle -type d -exec chmod 755 '{}' \\;\n#    fi\n    # But now we need to adjust the moodledata and the certs directory ownerships, and the permission for the generated config.php\n    sudo chown -R www-data.www-data /moodle/moodledata /moodle/certs\n    sudo chmod +r /moodle/html/moodle/config.php\n\n    # chmod /moodle for Azure NetApp Files (its default is 770!)\n    if [ $fileServerType = \"nfs-byo\" ]; then\n        sudo chmod +rx /moodle\n    fi\n\n   if [ $fileServerType = \"azurefiles\" ]; then\n      if [ \"$isMigration\" = \"true\" ]; then\n        echo -e '\\n\\rIts a migration flow, the moodle content is already on azure file share\\n\\r'\n      else\n         # Delayed copy of moodle installation to the Azure Files share\n\n         # First rename moodle directory to something else\n         mv /moodle /moodle_old_delete_me\n         # Then create the moodle share\n         echo -e '\\n\\rCreating an Azure Files share for moodle'\n         create_azure_files_moodle_share $storageAccountName $storageAccountKey /tmp/wabs.log $fileServerDiskSize\n         # Set up and mount Azure Files share. Must be done after nginx is installed because of www-data user/group\n         echo -e '\\n\\rSetting up and mounting Azure Files share on //'$storageAccountName'.file.core.windows.net/moodle on /moodle\\n\\r'\n         setup_and_mount_azure_files_moodle_share $storageAccountName $storageAccountKey\n         # Move the local installation over to the Azure Files\n         echo -e '\\n\\rMoving locally installed moodle over to Azure Files'\n\n         # install azcopy\n         wget -q -O azcopy_v10.tar.gz https://aka.ms/downloadazcopy-v10-linux && tar -xf azcopy_v10.tar.gz --strip-components=1 && mv ./azcopy /usr/bin/\n      \n         ACCOUNT_KEY=\"$storageAccountKey\"\n         NAME=\"$storageAccountName\"\n         END=`date -u -d \"60 minutes\" '+%Y-%m-%dT%H:%M:00Z'`\n\n         sas=$(az storage share generate-sas \\\n           -n moodle \\\n           --account-key $ACCOUNT_KEY \\\n           --account-name $NAME \\\n           --https-only \\\n           --permissions lrw \\\n           --expiry $END -o tsv)\n\n         export AZCOPY_CONCURRENCY_VALUE='48'\n         export AZCOPY_BUFFER_GB='4'\n\n         # cp -a /moodle_old_delete_me/* /moodle || true # Ignore case sensitive directory copy failure\n         azcopy --log-level ERROR copy \"/moodle_old_delete_me/*\" \"https://$NAME.file.core.windows.net/moodle?$sas\" --recursive || true # Ignore case sensitive directory copy failure\n         rm -rf /moodle_old_delete_me || true # Keep the files just in case\n      fi\n   fi\n\n   create_last_modified_time_update_script\n   run_once_last_modified_time_update_script\n\n   echo \"### Script End `date`###\"\n\n}  2>&1 | tee /tmp/install.log\n"
  },
  {
    "path": "scripts/install_tika.sh",
    "content": "#!/bin/bash\n# Custom Script for Linux\n\n# The MIT License (MIT)\n#\n# Permission is hereby granted, free of charge, to any person obtaining a copy\n# of this software and associated documentation files (the \"Software\"), to deal\n# in the Software without restriction, including without limitation the rights\n# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n# copies of the Software, and to permit persons to whom the Software is\n# furnished to do so, subject to the following conditions:\n# \n# The above copyright notice and this permission notice shall be included in all\n# copies or substantial portions of the Software.\n# \n# THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n# SOFTWARE.\n\ntikavmip=$1\n\necho $tikavmip     >> /tmp/vars.txt\n\n{\n\n  # make sure the system does automatic update\n  sudo apt-get -y update\n  sudo apt-get -y install unattended-upgrades\n\n  # download apache tika server\n  sudo wget -q http://mirrors.ocf.berkeley.edu/apache/tika/tika-server-1.18.jar --directory-prefix=/usr/share/java/\n\n  # install the required packages\n  sudo apt-get install -y openjdk-8-jre openjdk-8-jdk default-jre default-jdk\n\n  # Configure tika\n  cat <<EOF > /etc/systemd/system/tika-server.service\n[Unit]\nDescription = Java Service\nAfter network.target = tika-server.service\n\n[Service]\nType = forking\nExecStart = /usr/local/bin/tika-server start\nExecStop = /usr/local/bin/tika-server stop\nExecReload = /usr/local/bin/tika-server reload\n\n[Install]\nWantedBy=multi-user.target\nEOF\n\n  chmod 550 /etc/systemd/system/tika-server.service\n\n  cat <<EOF > /usr/local/bin/tika-server\n#!/bin/sh\nSERVICE_NAME=tika-server\nPATH_TO_JAR=/usr/share/java/tika-server-1.18.jar\nPID_PATH_NAME=/var/run/tika-server-pid\ncase \\$1 in\n    start)\n        echo \"Starting \\$SERVICE_NAME ...\"\n        if [ ! -f \\$PID_PATH_NAME ]; then\n            nohup java -jar \\$PATH_TO_JAR --host=$tikavmip --port=9998 >> /var/log/tika-server.out 2>&1&\n                        echo \\$! > \\$PID_PATH_NAME\n            echo \"\\$SERVICE_NAME started ...\"\n        else\n            echo \"\\$SERVICE_NAME is already running ...\"\n        fi\n    ;;\n    stop)\n        if [ -f \\$PID_PATH_NAME ]; then\n            PID=\\$(cat $PID_PATH_NAME);\n            echo \"\\$SERVICE_NAME stoping ...\"\n            kill \\$PID;\n            echo \"\\$SERVICE_NAME stopped ...\"\n            rm \\$PID_PATH_NAME\n        else\n            echo \"\\$SERVICE_NAME is not running ...\"\n        fi\n    ;;\n    restart)\n        if [ -f \\$PID_PATH_NAME ]; then\n            PID=\\$(cat $PID_PATH_NAME);\n            echo \"\\$SERVICE_NAME stopping ...\";\n            kill \\$PID;\n            echo \"\\$SERVICE_NAME stopped ...\";\n            rm \\$PID_PATH_NAME\n            echo \"\\$SERVICE_NAME starting ...\"\n            nohup java -jar \\$PATH_TO_JAR --host=$tikavmip --port=9998 >> /var/log/tika-server.out 2>&1&\n                        echo \\$! > \\$PID_PATH_NAME\n            echo \"\\$SERVICE_NAME started ...\"\n        else\n            echo \"\\$SERVICE_NAME is not running ...\"\n        fi\n    ;;\nesac\nEOF\n  chmod +x /usr/local/bin/tika-server\n  systemctl enable tika-server.service\n  systemctl start tika-server.service\n}  > /tmp/setup.log\n"
  },
  {
    "path": "scripts/setup_nfs_ha.sh",
    "content": "#!/bin/bash\n#\n# Script to set up highly available NFS server on an Ubuntu 16.04 (or higher) VM\n# that should be used on Azure with the custom script extension (which runs this script as root)\n#\n\nset -e\n\n# Parameters\nNODE1NAME=$1\nNODE1IP=$2\nNODE2NAME=$3\nNODE2IP=$4\nNFS_CLIENTS_IP_RANGE=$5     # E.g., \"10.0.0.0/24\". Can be \"*\", but strongly discouraged\n\n# This VM's IP address, to detect if this VM should be the master (Node 1 is the initial master)\nMY_IP=$(hostname -i)\n\n. ./helper_functions.sh\n\nfunction setup_required_packages\n{\n    apt_update_noninteractive\n    apt_install_noninteractive build-essential autoconf flex nfs-kernel-server corosync pacemaker resource-agents\n\n    # Shouldn't let systemd start nfs-kernel-server (Pacemaker should do that)\n    systemctl stop nfs-kernel-server\n    systemctl disable nfs-kernel-server\n\n    # Setup static port assignments for mountd, statd, quotad, nlm (tcp), and nlm (udp) respectively:\n\tsed -i 's/^\\(RPCMOUNTDOPTS=\"--manage-gids\\)\"/\\1 -p 2000\"/g' /etc/default/nfs-kernel-server\n    sed -i 's/^STATDOPTS=.*$/STATDOPTS=\"--port 2001 --outgoing-port 2002\"/' /etc/default/nfs-common\n    if [ -f /etc/default/quota ]; then\n        sed -i 's/^RPCQUOTADOPTS=.*$/RPCQUOTADOPTS=\"-p 2003\"/' /etc/default/quota\n    fi\n\tcat <<EOF > /etc/modprobe.d/azmdl-nfs-ports.conf\noptions lockd nlm_udpport=2004 nlm_tcpport=2004\noptions nfs callback_tcpport=2005\nEOF\n\n    cat <<EOF > /etc/sysctl.d/30-azmdl-nfs-ports.conf\nfs.nfs.nlm_tcpport=2004\nfs.nfs.nlm_udpport=2004\nEOF\n    # Reread modified sysctl settings for modified NFS static ports\n    sysctl --system\n\n    # Above alone still doesn't work for static ports. Try restarting related services.\n    systemctl try-restart nfs-config.service rpcbind.service rpc-statd.service nfs-server.service\n\n    # We need to install the \"azure-lb\" command separately if the resource-agents package didn't have it.\n    pushd /usr/lib/ocf/resource.d/heartbeat\n    if [ ! -e azure-lb ]; then\n        curl -LO https://raw.githubusercontent.com/ClusterLabs/resource-agents/master/heartbeat/azure-lb\n        chmod +x ./azure-lb\n    fi\n    popd\n}\n\nfunction setup_drbd_module_and_tools\n{\n    # We currently have to build the DRBD kernel module, as it's not included\n    # in the default linux-azure kernels or in any Azure extra packages.\n    # We should use the packaged DRBD module once Azure starts releasing\n    # extra modules packages that include DRBD module.\n\n    pushd /tmp\n    git clone http://github.com/LINBIT/drbd-9.0\n    git clone http://github.com/LINBIT/drbd-utils\n    cd drbd-9.0\n    make && make install\n    modprobe drbd\n    cd ../drbd-utils\n    ./autogen.sh\n    ./configure --prefix=/usr --localstatedir=/var --sysconfdir=/etc\n    make tools && make install-tools\n    cd ..\n    rm -rf drbd-9.0 drbd-utils\n    popd\n}\n\nfunction setup_drbd_with_disk\n{\n    local disk=$1\n    local node1name=$2\n    local node1ip=$3\n    local node2name=$4\n    local node2ip=$5\n    local drbd_resource_name=$6\n    local drbd_device_path=$7\n    local drbd_device_mount_point=$8\n\n    # Put LVM (to gain more flexibility) on the whole disk\n    local vgname=drbdvg\n    local lvname=drbdlv\n    wipefs -af $disk\n    pvcreate $disk\n    vgcreate $vgname $disk\n    lvcreate -n $lvname -l 95%VG $vgname\n\n    # Set up DRBD config\n    cat <<EOF > /etc/drbd.d/${drbd_resource_name}.res\nresource $drbd_resource_name {\n    device      ${drbd_device_path};\n    disk        /dev/${vgname}/${lvname};\n    meta-disk   internal;\n    disk {\n        c-fill-target 1M;\n        c-max-rate 110M;\n        c-min-rate 120K;\n    }\n    net {\n        max-buffers 20k;\n    }\n    on ${node1name} {\n        address ${node1ip}:7789;\n    }\n    on ${node2name} {\n        address ${node2ip}:7789;\n    }\n}\nEOF\n\n    # Initialize DRBD's metadata and bring up the device on both nodes\n    drbdadm create-md $drbd_resource_name\n    drbdadm up $drbd_resource_name\n    drbdadm status\n\n    # On the master (initially node 1) only, force DRBD into Primary and create an ext4 file system on the DRBD device\n    if [ \"$MY_IP\" = \"$NODE1IP\" ]; then\n        drbdadm primary $drbd_resource_name --force\n        mkfs.ext4 $drbd_device_path\n        mkdir -p $drbd_device_mount_point && mount $drbd_device_path $drbd_device_mount_point\n    fi\n}\n\nfunction setup_corosync_and_pacemaker_for_nfs\n{\n    local node1ip=$1\n    local node2ip=$2\n    local drbd_resource_name=$3     # E.g., azmdlr0\n    local drbd_device_path=$4       # E.g., /dev/drbd0\n    local drbd_mount_point=$5       # E.g., /drbd\n    local nfs_export_path=$6        # E.g., /drbd/moodle\n    local nfs_client_spec=$7        # E.g., * or 10.11.22.0/24\n\n    mv /etc/corosync/corosync.conf /etc/corosync/corosync.conf.orig || true\n    \n    local cluster_name=azmdl-cluster\n\n    cat <<EOF > /etc/corosync/corosync.conf\ntotem {\n    version: 2\n    secauth: off\n    cluster_name: ${cluster_name}\n    transport: udpu\n}\n\nnodelist {\n    node {\n        ring0_addr: ${node1ip}\n        nodeid: 1\n    }\n    node {\n        ring0_addr: ${node2ip}\n        nodeid: 2\n    }\n}\n\nquorum {\n    provider: corosync_votequorum\n    two_node:1\n}\n\nlogging {\n    to_syslog: yes\n}\nEOF\n\n    systemctl enable corosync pacemaker\n    systemctl restart corosync pacemaker\n\n    # TODO Should confirm if 'corosync-cfgtool -s' gives a non-loopback IP address (not 127.0.0.1), e.g.:\n    # $ corosync-cfgtool -s\n    # Printing ring status.\n    # Local node ID 2\n    # RING ID 0\n    #     id = 10.0.0.5\n    #     status = ring 0 active with no faults\n\n    # Finally, configure Pacemaker cluster resources, only on the initial master\n    if [ \"$MY_IP\" = \"$node1ip\" ]; then\n        mkdir -p ${nfs_export_path}\n        crm configure <<EOF\nprimitive p_drbd_r0 ocf:linbit:drbd \\\n    params drbd_resource=${drbd_resource_name} \\\n    op monitor interval=29s role=Master \\\n    op monitor interval=31s role=Slave \\\n    op start interval=0s timeout=240s \\\n    op stop interval=0s timeout=100s\nms ms_drbd_r0 p_drbd_r0 \\\n    meta master-max=1 master-node-max=1 clone-node-max=1 clone-max=2 notify=true\nprimitive p_fs_data Filesystem \\\n    params device=\"${drbd_device_path}\" directory=\"${drbd_mount_point}\" \\\n    fstype=ext4 options=noatime,nodiratime \\\n    op start interval=0s timeout=100s \\\n    op stop interval=0s timeout=100s \\\n    op monitor interval=10s timeout=100s\nprimitive p_nfsserver ocf:heartbeat:nfsserver \\\n    params nfs_shared_infodir=\"${drbd_mount_point}/nfs_shared_infodir\" \\\n    op start interval=0s timeout=40s \\\n    op stop interval=0s timeout=20s \\\n    op monitor interval=10s timeout=20s\nprimitive p_exportfs ocf:heartbeat:exportfs \\\n    params clientspec=\"${nfs_client_spec}\" directory=\"${nfs_export_path}\" fsid=1 \\\n    unlock_on_stop=1 options=rw,sync,no_root_squash \\\n    op start interval=0s timeout=40s \\\n    op stop interval=0s timeout=120s \\\n    op monitor interval=10s timeout=20s\nprimitive p_azure-lb azure-lb \\\n    params nc=\"/bin/nc\" port=\"61000\" \\\n    op start interval=0s timeout=20s \\\n    op stop interval=0s timeout=20s \\\n    op monitor interval=10s timeout=20s\ngroup g_services p_fs_data p_nfsserver p_exportfs p_azure-lb\ncolocation cl-g_services-with-ms_drbd_r0 inf: g_services ms_drbd_r0:Master\norder o-ms_drbd_r0-before-g_services inf: ms_drbd_r0:promote g_services:start\nproperty stonith-enabled=false\nEOF\n    fi\n    # TODO STONITH is disabled for now (two lines above). Should enable it soon.\n\n    # TODO 'crm status' should show the correctly configured/started cluster resources.\n}\n\n# Main\n\nsetup_required_packages\n\nsetup_drbd_module_and_tools\n\n# Don't create a file system, but just set up a disk (RAID if multiple unpartitioned disks)\n# AZMDL_DISK env var is set by the function as the discovered/initialized disk\nsetup_raid_disk_and_filesystem None /dev/md0 None False\n\nDRBD_RESOURCE_NAME=azmdlr0   # TODO Avoid hard-coded value\nDRBD_DEVICE_PATH=/dev/drbd0\nDRBD_MOUNT_POINT=/drbd       # TODO Avoid hard-coded value\n\nsetup_drbd_with_disk $AZMDL_DISK $NODE1NAME $NODE1IP $NODE2NAME $NODE2IP $DRBD_RESOURCE_NAME $DRBD_DEVICE_PATH $DRBD_MOUNT_POINT\n\nNFS_EXPORT_PATH=${DRBD_MOUNT_POINT}/data  # TODO Allow different export dir name -- This requires changes all along the pipeline, from the top-level template to this script...\n\nsetup_corosync_and_pacemaker_for_nfs $NODE1IP $NODE2IP $DRBD_RESOURCE_NAME $DRBD_DEVICE_PATH $DRBD_MOUNT_POINT $NFS_EXPORT_PATH \"$NFS_CLIENTS_IP_RANGE\"\n\necho \"NFS-HA setup succeeded. NFS_EXPORT_PATH=${NFS_EXPORT_PATH}, NFS_CLIENT_SPEC=${NFS_CLIENT_SPEC}\"\n"
  },
  {
    "path": "scripts/setup_webserver.sh",
    "content": "#!/bin/bash\n# Custom Script for Linux\n\n# The MIT License (MIT)\n#\n# Permission is hereby granted, free of charge, to any person obtaining a copy\n# of this software and associated documentation files (the \"Software\"), to deal\n# in the Software without restriction, including without limitation the rights\n# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n# copies of the Software, and to permit persons to whom the Software is\n# furnished to do so, subject to the following conditions:\n# \n# The above copyright notice and this permission notice shall be included in all\n# copies or substantial portions of the Software.\n# \n# THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n# SOFTWARE.\nset -ex\necho \"### Script Start $(date)###\"\n\nmoodle_on_azure_configs_json_path=${1}\n\n. ./helper_functions.sh\n\nget_setup_params_from_configs_json \"$moodle_on_azure_configs_json_path\" || exit 99\n\necho \"$glusterNode\"           >> /tmp/vars.txt\necho \"$glusterVolume\"         >> /tmp/vars.txt\necho \"$siteFQDN\"              >> /tmp/vars.txt\necho \"$httpsTermination\"      >> /tmp/vars.txt\necho \"$syslogServer\"          >> /tmp/vars.txt\necho \"$webServerType\"         >> /tmp/vars.txt\necho \"$dbServerType\"          >> /tmp/vars.txt\necho \"$fileServerType\"        >> /tmp/vars.txt\necho \"$storageAccountName\"    >> /tmp/vars.txt\necho \"$storageAccountKey\"     >> /tmp/vars.txt\necho \"$nfsVmName\"             >> /tmp/vars.txt\necho \"$nfsByoIpExportPath\"    >> /tmp/vars.txt\necho \"$htmlLocalCopySwitch\"   >> /tmp/vars.txt\necho \"$phpVersion\"            >> /tmp/vars.txt\n\n\n\ncheck_fileServerType_param \"$fileServerType\"\n\n{\n  set -ex\n  echo \"### Function Start $(date)###\"\n\n  # add azure-cli repository && install azure cli\n  AZ_REPO=$(lsb_release -cs)\n  mkdir -p /etc/apt/keyrings\n  curl -sLS https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor > /etc/apt/keyrings/microsoft.gpg && chmod go+r /etc/apt/keyrings/microsoft.gpg\n  echo \"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/microsoft.gpg] https://packages.microsoft.com/repos/azure-cli/ $AZ_REPO main\" > /etc/apt/sources.list.d/azure-cli.list\n\n  apt_update_noninteractive\n\n  # install pre-requisites including VARNISH and PHP-FPM\n\n  apt_install_noninteractive \\\n    azure-cli \\\n    ca-certificates \\\n    curl \\\n    apt-transport-https \\\n    lsb-release gnupg \\\n    software-properties-common \\\n    unzip \\\n    rsyslog \\\n    postgresql-client \\\n    mysql-client \\\n    git \\\n    unattended-upgrades \\\n    tuned \\\n    varnish \\\n    php \\\n    php-cli \\\n    php-curl \\\n    php-zip \\\n    php-pear \\\n    php-mbstring \\\n    mcrypt \\\n    php-dev \\\n    graphviz \\\n    aspell \\\n    php-soap \\\n    php-json \\\n    php-redis \\\n    php-bcmath \\\n    php-ldap \\\n    php-gd \\\n    php-pgsql \\\n    php-mysql \\\n    php-xmlrpc \\\n    php-intl \\\n    php-xml \\\n    php-bz2\n\n  # install azcopy\n  wget -q -O azcopy_v10.tar.gz https://aka.ms/downloadazcopy-v10-linux && tar -xf azcopy_v10.tar.gz --strip-components=1 && mv ./azcopy /usr/bin/\n\n  # kernel settings\n  cat <<EOF > /etc/sysctl.d/99-network-performance.conf\nnet.core.somaxconn = 65536\nnet.core.netdev_max_backlog = 5000\nnet.core.rmem_max = 16777216\nnet.core.wmem_max = 16777216\nnet.ipv4.tcp_wmem = 4096 12582912 16777216\nnet.ipv4.tcp_rmem = 4096 12582912 16777216\nnet.ipv4.route.flush = 1\nnet.ipv4.tcp_max_syn_backlog = 8096\nnet.ipv4.tcp_tw_reuse = 1\nnet.ipv4.ip_local_port_range = 10240 65535\nEOF\n  # apply the new kernel settings\n  sysctl -p /etc/sysctl.d/99-network-performance.conf\n\n  # scheduling IRQ interrupts on the last two cores of the cpu\n  # masking 0011 or 00000011 the result will always be 3 echo \"obase=16;ibase=2;0011\" | bc | tr '[:upper:]' '[:lower:]'\n  if [ -f /etc/default/irqbalance ]; then\n    sed -i \"s/\\#IRQBALANCE_BANNED_CPUS\\=/IRQBALANCE_BANNED_CPUS\\=3/g\" /etc/default/irqbalance\n    systemctl restart irqbalance.service \n  fi\n\n  # configuring tuned for throughput-performance\n  systemctl enable tuned\n  tuned-adm profile throughput-performance\n\n  if [ \"$fileServerType\" = \"gluster\" ]; then\n    #configure gluster repository & install gluster client\n    add-apt-repository ppa:gluster/glusterfs-9 --yes\n    apt_update_noninteractive\n    apt_install_noninteractive glusterfs-client\n  elif [ \"$fileServerType\" = \"azurefiles\" ]; then\n    apt_install_noninteractive \\\n      linux-modules-extra-azure \\\n      cifs-utils >> /tmp/apt.log\n      \n      # Because of https://bugs.launchpad.net/ubuntu/+source/linux-azure/+bug/2042092\n      modprobe cifs && true\n  fi\n\n  if [ \"$webServerType\" = \"nginx\" -o \"$httpsTermination\" = \"VMSS\" ]; then\n    apt_install_noninteractive nginx\n  fi\n   \n  if [ \"$webServerType\" = \"apache\" ]; then\n    # install apache pacakges\n    apt_install_noninteractive apache2 libapache2-mod-php\n  else\n    # for nginx-only option\n    apt_install_noninteractive php-fpm\n  fi\n\n  # Moodle requirements\n  if [ \"$dbServerType\" = \"mssql\" ]; then\n    install_php_mssql_driver\n  fi\n\n  # PHP Version\n  PhpVer=$(get_php_version)\n\n  if [ \"$fileServerType\" = \"gluster\" ]; then\n    # Mount gluster fs for /moodle\n    sudo mkdir -p /moodle\n    sudo chown www-data /moodle\n    sudo chmod 770 /moodle\n    sudo echo -e 'Adding Gluster FS to /etc/fstab and mounting it'\n    setup_and_mount_gluster_moodle_share \"$glusterNode\" \"$glusterVolume\"\n  elif [ $fileServerType = \"nfs\" ]; then\n    # mount NFS export (set up on controller VM--No HA)\n    echo -e '\\n\\rMounting NFS export from '$nfsVmName':/moodle on /moodle and adding it to /etc/fstab\\n\\r'\n    configure_nfs_client_and_mount $nfsVmName /moodle /moodle\n  elif [ $fileServerType = \"nfs-ha\" ]; then\n    # mount NFS-HA export\n    echo -e '\\n\\rMounting NFS export from '$nfsHaLbIP':'$nfsHaExportPath' on /moodle and adding it to /etc/fstab\\n\\r'\n    configure_nfs_client_and_mount $nfsHaLbIP $nfsHaExportPath /moodle\n  elif [ $fileServerType = \"nfs-byo\" ]; then\n    # mount NFS-BYO export\n    echo -e '\\n\\rMounting NFS export from '$nfsByoIpExportPath' on /moodle and adding it to /etc/fstab\\n\\r'\n    configure_nfs_client_and_mount0 $nfsByoIpExportPath /moodle\n  else # \"azurefiles\"\n    setup_and_mount_azure_files_moodle_share $storageAccountName $storageAccountKey\n  fi\n\n  # Configure syslog to forward\n  cat <<EOF >> /etc/rsyslog.conf\n\\$ModLoad imudp\n\\$UDPServerRun 514\nEOF\n  cat <<EOF >> /etc/rsyslog.d/40-remote.conf\nlocal1.*   @${syslogServer}:514\nlocal2.*   @${syslogServer}:514\nEOF\n  service syslog restart\n\n  if [ \"$webServerType\" = \"nginx\" -o \"$httpsTermination\" = \"VMSS\" ]; then\n    # Build nginx config\n    cat <<EOF > /etc/nginx/nginx.conf\nuser www-data;\nworker_processes auto;\npid /run/nginx.pid;\n\nevents {\n  worker_connections 8192;\n  multi_accept on;\n  use epoll;\n}\n\nworker_rlimit_nofile 100000;\n\nhttp {\n\n  sendfile on;\n  tcp_nopush on;\n  tcp_nodelay on;\n  keepalive_timeout 65;\n  types_hash_max_size 2048;\n  client_max_body_size 0;\n  proxy_max_temp_file_size 0;\n  server_names_hash_bucket_size  128;\n  fastcgi_buffers 16 16k; \n  fastcgi_buffer_size 32k;\n  proxy_buffering off;\n  include /etc/nginx/mime.types;\n  default_type application/octet-stream;\n\n  access_log /var/log/nginx/access.log;\n  error_log /var/log/nginx/error.log;\n\n  open_file_cache max=20000 inactive=20s;\n  open_file_cache_valid 30s;\n  open_file_cache_min_uses 2;\n  open_file_cache_errors on;\n\n  set_real_ip_from   127.0.0.1;\n  real_ip_header      X-Forwarded-For;\n  #upgrading to TLSv1.2 and droping 1 & 1.1\n  ssl_protocols TLSv1.2 TLSv1.3;\n  ssl_prefer_server_ciphers off;\n  #adding ssl ciphers\n  ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;\n\n  gzip on;\n  gzip_disable \"msie6\";\n  gzip_vary on;\n  gzip_proxied any;\n  gzip_comp_level 6;\n  gzip_buffers 16 8k;\n  gzip_http_version 1.1;\n  gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript;\nEOF\n    if [ \"$httpsTermination\" != \"None\" ]; then\n      cat <<EOF >> /etc/nginx/nginx.conf\n  map \\$http_x_forwarded_proto \\$fastcgi_https {                                                                                          \n    default \\$https;                                                                                                                   \n    http '';                                                                                                                          \n    https on;                                                                                                                         \n  }\nEOF\n    fi\n\n    cat <<EOF >> /etc/nginx/nginx.conf\n  log_format moodle_combined '\\$remote_addr - \\$upstream_http_x_moodleuser [\\$time_local] '\n                             '\"\\$request\" \\$status \\$body_bytes_sent '\n                             '\"\\$http_referer\" \"\\$http_user_agent\"';\n\n\n  include /etc/nginx/conf.d/*.conf;\n  include /etc/nginx/sites-enabled/*;\n}\nEOF\n  fi # if [ \"$webServerType\" = \"nginx\" -o \"$httpsTermination\" = \"VMSS\" ];\n\n  # Set up html dir local copy if specified\n  htmlRootDir=\"/moodle/html/moodle\"\n  if [ \"$htmlLocalCopySwitch\" = \"true\" ]; then\n    if [ \"$fileServerType\" = \"azurefiles\" ]; then\n      mkdir -p /var/www/html\n      ACCOUNT_KEY=\"$storageAccountKey\"\n      NAME=\"$storageAccountName\"\n      END=$(date -u -d \"60 minutes\" '+%Y-%m-%dT%H:%M:00Z')\n      htmlRootDir=\"/var/www/html/moodle\"\n\n      sas=$(az storage share generate-sas \\\n        -n moodle \\\n        --account-key $ACCOUNT_KEY \\\n        --account-name $NAME \\\n        --https-only \\\n        --permissions lr \\\n        --expiry $END -o tsv)\n\n      export AZCOPY_CONCURRENCY_VALUE='48'\n      export AZCOPY_BUFFER_GB='4'\n\n      azcopy --log-level ERROR copy \"https://$NAME.file.core.windows.net/moodle/html/moodle/*?$sas\" $htmlRootDir --recursive\n      chown www-data:www-data -R $htmlRootDir && sync\n      setup_html_local_copy_cron_job\n    fi\n    if [ \"$fileServerType\" = \"nfs\" -o \"$fileServerType\" = \"nfs-ha\" -o \"$fileServerType\" = \"nfs-byo\" -o \"$fileServerType\" = \"gluster\" ]; then\n      mkdir -p /var/www/html/moodle\n      rsync -a /moodle/html/moodle/ $htmlRootDir/\n      chown www-data:www-data -R $htmlRootDir && sync\n      setup_html_local_copy_cron_job\n    fi\n  fi\n\n  if [ \"$httpsTermination\" = \"VMSS\" ]; then\n    # Configure nginx/https\n    cat <<EOF >> /etc/nginx/sites-enabled/${siteFQDN}.conf\nserver {\n        listen 443 ssl http2;\n        root ${htmlRootDir};\n\t      index index.php index.html index.htm;\n\n        ssl_certificate /moodle/certs/nginx.crt;\n        ssl_certificate_key /moodle/certs/nginx.key;\n        ssl_session_timeout 1d;\n        ssl_session_cache shared:MozSSL:10m;  # about 40000 sessions\n        ssl_session_tickets off;\n\n        # Log to syslog\n        error_log syslog:server=localhost,facility=local1,severity=error,tag=moodle;\n        access_log syslog:server=localhost,facility=local1,severity=notice,tag=moodle moodle_combined;\n\n        # Log XFF IP instead of varnish\n        set_real_ip_from    10.0.0.0/8;\n        set_real_ip_from    127.0.0.1;\n        set_real_ip_from    172.16.0.0/12;\n        set_real_ip_from    192.168.0.0/16;\n        real_ip_header      X-Forwarded-For;\n        real_ip_recursive   on;\n\n        location / {\n          proxy_set_header Host \\$host;\n          proxy_set_header HTTP_REFERER \\$http_referer;\n          proxy_set_header X-Forwarded-Host \\$host;\n          proxy_set_header X-Forwarded-Server \\$host;\n          proxy_set_header X-Forwarded-Proto https;\n          proxy_set_header X-Forwarded-For \\$proxy_add_x_forwarded_for;\n          proxy_pass http://localhost:80;\n\n          proxy_next_upstream error timeout http_502 http_504;\n          proxy_connect_timeout       3600;\n          proxy_send_timeout          3600;\n          proxy_read_timeout          3600;\n          send_timeout                3600;\n        }\n}\n\nEOF\n  fi\n\n  if [ \"$webServerType\" = \"nginx\" ]; then\n    cat <<EOF >> /etc/nginx/sites-enabled/${siteFQDN}.conf\nserver {\n        listen 81 default;\n        server_name ${siteFQDN};\n        root ${htmlRootDir};\n        index index.php index.html index.htm;\n\n        # Log to syslog\n        error_log syslog:server=localhost,facility=local1,severity=error,tag=moodle;\n        access_log syslog:server=localhost,facility=local1,severity=notice,tag=moodle moodle_combined;\n\n        # Log XFF IP instead of varnish\n        set_real_ip_from    10.0.0.0/8;\n        set_real_ip_from    127.0.0.1;\n        set_real_ip_from    172.16.0.0/12;\n        set_real_ip_from    192.168.0.0/16;\n        real_ip_header      X-Forwarded-For;\n        real_ip_recursive   on;\nEOF\n    if [ \"$httpsTermination\" != \"None\" ]; then\n      cat <<EOF >> /etc/nginx/sites-enabled/${siteFQDN}.conf\n        # Redirect to https\n        if (\\$http_x_forwarded_proto != https) {\n                return 301 https://\\$server_name\\$request_uri;\n        }\n        rewrite ^/(.*\\.php)(/)(.*)$ /\\$1?file=/\\$3 last;\nEOF\n    fi\n    cat <<EOF >> /etc/nginx/sites-enabled/${siteFQDN}.conf\n        # Filter out php-fpm status page\n        location ~ ^/server-status {\n            return 404;\n        }\n\n  location / {\n    try_files \\$uri \\$uri/index.php?\\$query_string;\n  }\n \n        location ~ [^/]\\.php(/|$) {\n          fastcgi_split_path_info ^(.+?\\.php)(/.*)$;\n          if (!-f \\$document_root\\$fastcgi_script_name) {\n                  return 404;\n          }\n \n          fastcgi_buffers 16 16k;\n          fastcgi_buffer_size 32k;\n          fastcgi_param SCRIPT_FILENAME \\$document_root\\$fastcgi_script_name;\n          fastcgi_pass backend;\n          fastcgi_param PATH_INFO \\$fastcgi_path_info;\n          fastcgi_read_timeout 3600;\n          fastcgi_index index.php;\n          include fastcgi_params;\n        }\n}\n\nupstream backend {\n        server unix:/run/php/php${PhpVer}-fpm.sock fail_timeout=1s;\n        server unix:/run/php/php${PhpVer}-fpm-backup.sock backup;\n}\n\nEOF\n  fi\n\n  if [ \"$webServerType\" = \"apache\" ]; then\n    # Configure Apache/php\n    sed -i \"s/Listen 80/Listen 81/\" /etc/apache2/ports.conf\n    a2enmod rewrite && a2enmod remoteip && a2enmod headers\n\n    cat <<EOF >> /etc/apache2/sites-enabled/${siteFQDN}.conf\n<VirtualHost *:81>\n  ServerName ${siteFQDN}\n\n  ServerAdmin webmaster@localhost\n  DocumentRoot ${htmlRootDir}\n\n  <Directory ${htmlRootDir}>\n    Options FollowSymLinks\n    AllowOverride All\n    Require all granted\n  </Directory>\nEOF\n    if [ \"$httpsTermination\" != \"None\" ]; then\n      cat <<EOF >> /etc/apache2/sites-enabled/${siteFQDN}.conf\n    # Redirect unencrypted direct connections to HTTPS\n    <IfModule mod_rewrite.c>\n      RewriteEngine on\n      RewriteCond %{HTTP:X-Forwarded-Proto} !https [NC]\n      RewriteRule ^ https://%{SERVER_NAME}%{REQUEST_URI} [L,R=301]\n    </IFModule>\nEOF\n    fi\n    cat <<EOF >> /etc/apache2/sites-enabled/${siteFQDN}.conf\n    # Log X-Forwarded-For IP address instead of varnish (127.0.0.1)\n    SetEnvIf X-Forwarded-For \"^.*\\..*\\..*\\..*\" forwarded\n    LogFormat \"%h %l %u %t \\\"%r\\\" %>s %b \\\"%{Referer}i\\\" \\\"%{User-Agent}i\\\"\" combined\n    LogFormat \"%{X-Forwarded-For}i %l %u %t \\\"%r\\\" %>s %b \\\"%{Referer}i\\\" \\\"%{User-Agent}i\\\"\" forwarded\n    ErrorLog \"|/usr/bin/logger -t moodle -p local1.error\"\n    CustomLog \"|/usr/bin/logger -t moodle -p local1.notice\" combined env=!forwarded\n    CustomLog \"|/usr/bin/logger -t moodle -p local1.notice\" forwarded env=forwarded\n\n</VirtualHost>\nEOF\n  fi # if [ \"$webServerType\" = \"apache\" ];\n\n   # php config \n   if [ \"$webServerType\" = \"apache\" ]; then\n     PhpIni=/etc/php/${PhpVer}/apache2/php.ini\n   else\n     PhpIni=/etc/php/${PhpVer}/fpm/php.ini\n   fi\n   sed -i \"s/memory_limit.*/memory_limit = 512M/\" $PhpIni\n   sed -i \"s/max_execution_time.*/max_execution_time = 18000/\" $PhpIni\n   sed -i \"s/;max_input_vars.*/max_input_vars = 100000/\" $PhpIni\n   sed -i \"s/max_input_time.*/max_input_time = 600/\" $PhpIni\n   sed -i \"s/upload_max_filesize.*/upload_max_filesize = 1024M/\" $PhpIni\n   sed -i \"s/post_max_size.*/post_max_size = 1056M/\" $PhpIni\n   sed -i \"s/;opcache.use_cwd.*/opcache.use_cwd = 1/\" $PhpIni\n   sed -i \"s/;opcache.validate_timestamps.*/opcache.validate_timestamps = 1/\" $PhpIni\n   sed -i \"s/;opcache.save_comments.*/opcache.save_comments = 1/\" $PhpIni\n   sed -i \"s/;opcache.enable_file_override.*/opcache.enable_file_override = 0/\" $PhpIni\n   sed -i \"s/;opcache.enable.*/opcache.enable = 1/\" $PhpIni\n   sed -i \"s/;opcache.memory_consumption.*/opcache.memory_consumption = 512/\" $PhpIni\n   sed -i \"s/;opcache.max_accelerated_files.*/opcache.max_accelerated_files = 20000/\" $PhpIni\n\n   # Remove the default site. Moodle is the only site we want\n   rm -f /etc/nginx/sites-enabled/default\n   if [ \"$webServerType\" = \"apache\" ]; then\n     rm -f /etc/apache2/sites-enabled/000-default.conf\n   fi\n\n   if [ \"$webServerType\" = \"nginx\" -o \"$httpsTermination\" = \"VMSS\" ]; then\n     # update startup script to wait for certificate in /moodle mount\n     setup_moodle_mount_dependency_for_systemd_service nginx || exit 1\n     # restart Nginx\n     sudo service nginx restart \n   fi\n\n   # Configure varnish startup for 18.04\n   VARNISHSTART=\"ExecStart=\\/usr\\/sbin\\/varnishd -j unix,user=vcache -F -a :80 -T localhost:6082 -f \\/etc\\/varnish\\/moodle.vcl -S \\/etc\\/varnish\\/secret -s malloc,4096m -p thread_pool_min=1000 -p thread_pool_max=4000 -p thread_pool_add_delay=0.1 -p timeout_linger=10 -p timeout_idle=30 -p send_timeout=1800 -p thread_pools=2 -p http_max_hdr=512 -p workspace_backend=512k\"\n   sed -i \"s/^ExecStart.*/${VARNISHSTART}/\" /lib/systemd/system/varnish.service\n\n   # Configure varnish VCL for moodle\n   cat <<EOF >> /etc/varnish/moodle.vcl\nvcl 4.0;\n\nimport std;\nimport directors;\nbackend default {\n    .host = \"localhost\";\n    .port = \"81\";\n    .first_byte_timeout = 3600s;\n    .connect_timeout = 600s;\n    .between_bytes_timeout = 600s;\n}\n\nsub vcl_recv {\n    # Varnish does not support SPDY or HTTP/2.0 untill we upgrade to Varnish 5.0\n    if (req.method == \"PRI\") {\n        return (synth(405));\n    }\n\n    if (req.restarts == 0) {\n      if (req.http.X-Forwarded-For) {\n        set req.http.X-Forwarded-For = req.http.X-Forwarded-For + \", \" + client.ip;\n      } else {\n        set req.http.X-Forwarded-For = client.ip;\n      }\n    }\n\n    # Non-RFC2616 or CONNECT HTTP requests methods filtered. Pipe requests directly to backend\n    if (req.method != \"GET\" &&\n        req.method != \"HEAD\" &&\n        req.method != \"PUT\" &&\n        req.method != \"POST\" &&\n        req.method != \"TRACE\" &&\n        req.method != \"OPTIONS\" &&\n        req.method != \"DELETE\") {\n      return (pipe);\n    }\n\n    # Varnish don't mess with healthchecks\n    if (req.url ~ \"^/admin/tool/heartbeat\" || req.url ~ \"^/healthcheck.php\")\n    {\n        return (pass);\n    }\n\n    # Pipe requests to backup.php straight to backend - prevents problem with progress bar long polling 503 problem\n    # This is here because backup.php is POSTing to itself - Filter before !GET&&!HEAD\n    if (req.url ~ \"^/backup/backup.php\")\n    {\n        return (pipe);\n    }\n\n    # Varnish only deals with GET and HEAD by default. If request method is not GET or HEAD, pass request to backend\n    if (req.method != \"GET\" && req.method != \"HEAD\") {\n      return (pass);\n    }\n\n    ### Rules for Moodle and Totara sites ###\n    # Moodle doesn't require Cookie to serve following assets. Remove Cookie header from request, so it will be looked up.\n    if ( req.url ~ \"^/altlogin/.+/.+\\.(png|jpg|jpeg|gif|css|js|webp)$\" ||\n         req.url ~ \"^/pix/.+\\.(png|jpg|jpeg|gif)$\" ||\n         req.url ~ \"^/theme/font.php\" ||\n         req.url ~ \"^/theme/image.php\" ||\n         req.url ~ \"^/theme/javascript.php\" ||\n         req.url ~ \"^/theme/jquery.php\" ||\n         req.url ~ \"^/theme/styles.php\" ||\n         req.url ~ \"^/theme/yui\" ||\n         req.url ~ \"^/lib/javascript.php/-1/\" ||\n         req.url ~ \"^/lib/requirejs.php/-1/\"\n        )\n    {\n        set req.http.X-Long-TTL = \"86400\";\n        unset req.http.Cookie;\n        return(hash);\n    }\n\n    # Perform lookup for selected assets that we know are static but Moodle still needs a Cookie\n    if(  req.url ~ \"^/theme/.+\\.(png|jpg|jpeg|gif|css|js|webp)\" ||\n         req.url ~ \"^/lib/.+\\.(png|jpg|jpeg|gif|css|js|webp)\" ||\n         req.url ~ \"^/pluginfile.php/[0-9]+/course/overviewfiles/.+\\.(?i)(png|jpg)$\"\n      )\n    {\n         # Set internal temporary header, based on which we will do things in vcl_backend_response\n         set req.http.X-Long-TTL = \"86400\";\n         return (hash);\n    }\n\n    # Serve requests to SCORM checknet.txt from varnish. Have to remove get parameters. Response body always contains \"1\"\n    if ( req.url ~ \"^/lib/yui/build/moodle-core-checknet/assets/checknet.txt\" )\n    {\n        set req.url = regsub(req.url, \"(.*)\\?.*\", \"\\1\");\n        unset req.http.Cookie; # Will go to hash anyway at the end of vcl_recv\n        set req.http.X-Long-TTL = \"86400\";\n        return(hash);\n    }\n\n    # Requests containing \"Cookie\" or \"Authorization\" headers will not be cached\n    if (req.http.Authorization || req.http.Cookie) {\n        return (pass);\n    }\n\n    # Almost everything in Moodle correctly serves Cache-Control headers, if\n    # needed, which varnish will honor, but there are some which don't. Rather\n    # than explicitly finding them all and listing them here we just fail safe\n    # and don't cache unknown urls that get this far.\n    return (pass);\n}\n\nsub vcl_backend_response {\n    # Happens after we have read the response headers from the backend.\n    # \n    # Here you clean the response headers, removing silly Set-Cookie headers\n    # and other mistakes your backend does.\n\n    # We know these assest are static, let's set TTL >0 and allow client caching\n    if ( beresp.http.Cache-Control && bereq.http.X-Long-TTL && beresp.ttl < std.duration(bereq.http.X-Long-TTL + \"s\", 1s) && !beresp.http.WWW-Authenticate )\n    { # If max-age < defined in X-Long-TTL header\n        set beresp.http.X-Orig-Pragma = beresp.http.Pragma; unset beresp.http.Pragma;\n        set beresp.http.X-Orig-Cache-Control = beresp.http.Cache-Control;\n        set beresp.http.Cache-Control = \"public, max-age=\"+bereq.http.X-Long-TTL+\", no-transform\";\n        set beresp.ttl = std.duration(bereq.http.X-Long-TTL + \"s\", 1s);\n        unset bereq.http.X-Long-TTL;\n    }\n    else if( !beresp.http.Cache-Control && bereq.http.X-Long-TTL && !beresp.http.WWW-Authenticate ) {\n        set beresp.http.X-Orig-Pragma = beresp.http.Pragma; unset beresp.http.Pragma;\n        set beresp.http.Cache-Control = \"public, max-age=\"+bereq.http.X-Long-TTL+\", no-transform\";\n        set beresp.ttl = std.duration(bereq.http.X-Long-TTL + \"s\", 1s);\n        unset bereq.http.X-Long-TTL;\n    }\n    else { # Don't touch headers if max-age > defined in X-Long-TTL header\n        unset bereq.http.X-Long-TTL;\n    }\n\n    # Here we set X-Trace header, prepending it to X-Trace header received from backend. Useful for troubleshooting\n    if(beresp.http.x-trace && !beresp.was_304) {\n        set beresp.http.X-Trace = regsub(server.identity, \"^([^.]+),?.*$\", \"\\1\")+\"->\"+regsub(beresp.backend.name, \"^(.+)\\((?:[0-9]{1,3}\\.){3}([0-9]{1,3})\\)\",\"\\1(\\2)\")+\"->\"+beresp.http.X-Trace;\n    }\n    else {\n        set beresp.http.X-Trace = regsub(server.identity, \"^([^.]+),?.*$\", \"\\1\")+\"->\"+regsub(beresp.backend.name, \"^(.+)\\((?:[0-9]{1,3}\\.){3}([0-9]{1,3})\\)\",\"\\1(\\2)\");\n    }\n\n    # Gzip JS, CSS is done at the ngnix level doing it here dosen't respect the no buffer requsets\n    # if (beresp.http.content-type ~ \"application/javascript.*\" || beresp.http.content-type ~ \"text\") {\n    #    set beresp.do_gzip = true;\n    #}\n}\n\nsub vcl_deliver {\n\n    # Revert back to original Cache-Control header before delivery to client\n    if (resp.http.X-Orig-Cache-Control)\n    {\n        set resp.http.Cache-Control = resp.http.X-Orig-Cache-Control;\n        unset resp.http.X-Orig-Cache-Control;\n    }\n\n    # Revert back to original Pragma header before delivery to client\n    if (resp.http.X-Orig-Pragma)\n    {\n        set resp.http.Pragma = resp.http.X-Orig-Pragma;\n        unset resp.http.X-Orig-Pragma;\n    }\n\n    # (Optional) X-Cache HTTP header will be added to responce, indicating whether object was retrieved from backend, or served from cache\n    if (obj.hits > 0) {\n        set resp.http.X-Cache = \"HIT\";\n    } else {\n        set resp.http.X-Cache = \"MISS\";\n    }\n\n    # Set X-AuthOK header when totara/varnsih authentication succeeded\n    if (req.http.X-AuthOK) {\n        set resp.http.X-AuthOK = req.http.X-AuthOK;\n    }\n\n    # If desired \"Via: 1.1 Varnish-v4\" response header can be removed from response\n    unset resp.http.Via;\n    unset resp.http.Server;\n\n    return(deliver);\n}\n\nsub vcl_backend_error {\n    # More comprehensive varnish error page. Display time, instance hostname, host header, url for easier troubleshooting.\n    set beresp.http.Content-Type = \"text/html; charset=utf-8\";\n    set beresp.http.Retry-After = \"5\";\n    synthetic( {\"\n  <!DOCTYPE html>\n  <html>\n    <head>\n      <title>\"} + beresp.status + \" \" + beresp.reason + {\"</title>\n    </head>\n    <body>\n      <h1>Error \"} + beresp.status + \" \" + beresp.reason + {\"</h1>\n      <p>\"} + beresp.reason + {\"</p>\n      <h3>Guru Meditation:</h3>\n      <p>Time: \"} + now + {\"</p>\n      <p>Node: \"} + server.hostname + {\"</p>\n      <p>Host: \"} + bereq.http.host + {\"</p>\n      <p>URL: \"} + bereq.url + {\"</p>\n      <p>XID: \"} + bereq.xid + {\"</p>\n      <hr>\n      <p>Varnish cache server\n    </body>\n  </html>\n  \"} );\n   return (deliver);\n}\n\nsub vcl_synth {\n\n    #Redirect using '301 - Permanent Redirect', permanent redirect\n    if (resp.status == 851) { \n        set resp.http.Location = req.http.x-redir;\n        set resp.http.X-Varnish-Redirect = true;\n        set resp.status = 301;\n        return (deliver);\n    }\n\n    #Redirect using '302 - Found', temporary redirect\n    if (resp.status == 852) { \n        set resp.http.Location = req.http.x-redir;\n        set resp.http.X-Varnish-Redirect = true;\n        set resp.status = 302;\n        return (deliver);\n    }\n\n    #Redirect using '307 - Temporary Redirect', !GET&&!HEAD requests, dont change method on redirected requests\n    if (resp.status == 857) { \n        set resp.http.Location = req.http.x-redir;\n        set resp.http.X-Varnish-Redirect = true;\n        set resp.status = 307;\n        return (deliver);\n    }\n\n    #Respond with 403 - Forbidden\n    if (resp.status == 863) {\n        set resp.http.X-Varnish-Error = true;\n        set resp.status = 403;\n        return (deliver);\n    }\n}\nEOF\n\n# This code is stop apache2 which is installing in 18.04\n  service=apache2\n  if [ \"$webServerType\" = \"nginx\" ]; then\n      if pgrep -x \"$service\" >/dev/null \n      then\n            echo \"Stop the $service!!!\"\n            systemctl stop $service\n      else\n            systemctl mask $service\n      fi\n  fi\n  # Restart Varnish\n  systemctl daemon-reload\n  systemctl restart varnish\n\n   if [ \"$webServerType\" = \"nginx\" ]; then\n     # fpm config - overload this \n     cat <<EOF > /etc/php/${PhpVer}/fpm/pool.d/www.conf\n[www]\nuser = www-data\ngroup = www-data\nlisten = /run/php/php${PhpVer}-fpm.sock\nlisten.owner = www-data\nlisten.group = www-data\npm = static\npm.max_children = 32\npm.start_servers = 32\npm.max_requests = 300000\nEOF\n\ncat <<EOF > /etc/php/${PhpVer}/fpm/pool.d/backup.conf\n[backup]\nuser = www-data\ngroup = www-data\nlisten = /run/php/php${PhpVer}-fpm-backup.sock\nlisten.owner = www-data\nlisten.group = www-data\npm = static\npm.max_children = 16\npm.start_servers = 16\npm.max_requests = 300000\nEOF\n\n     # Restart fpm\n     service php${PhpVer}-fpm restart\n   fi\n\n   if [ \"$webServerType\" = \"apache\" ]; then\n      if [ \"$htmlLocalCopySwitch\" != \"true\" ]; then\n        setup_moodle_mount_dependency_for_systemd_service apache2 || exit 1\n      fi\n        service apache2 restart\n   fi\n\n  echo \"### Script End $(date)###\"\n} 2>&1 | tee /tmp/setup.log\n"
  }
]