diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..4a6114fb --- /dev/null +++ b/.gitignore @@ -0,0 +1,117 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +env/ +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +.hypothesis/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# pyenv +.python-version + +# celery beat schedule file +celerybeat-schedule + +# SageMath parsed files +*.sage.py + +# dotenv +.env + +# virtualenv +.venv +venv/ +ENV/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ + +# test outputs +loadtest/test_outputs/ + +# Emacs +*#*# + +# Node Modules +node_modules + +# Eclipse +.project + +# Specific azuredeploy.parameters.json files for dev testing (ignored not to expose ssh pub keys) +.params/ +.vs/ diff --git a/.jshintrc b/.jshintrc new file mode 100644 index 00000000..14987e01 --- /dev/null +++ b/.jshintrc @@ -0,0 +1,28 @@ +{ + "esnext": true, + "node": true, + "browser": true, + "nomen": false, + "bitwise": true, + "eqeqeq": true, + "forin": true, + "immed": true, + "latedef": true, + "newcap": true, + "noarg": true, + "noempty": true, + "nonew": true, + "plusplus": true, + "regexp": true, + "undef": true, + "unused": true, + "trailing": true, + "indent": 4, + "esnext": true, + "onevar": true, + "white": true, + "quotmark": "double", + "predef": { + } +} + diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 00000000..7b5fb43d --- /dev/null +++ b/.travis.yml @@ -0,0 +1,29 @@ +dist: trusty + +language: python + +node_js: "0.12" + +python: "3.5" + +cache: + - directories: node_modules + - pip + +env: + - PYTHONUNBUFFERED=TRUE + +install: + - npm install # Install task runners for lint checking. + - pip install azure-mgmt-subscription azure-mgmt-resource keyring pycurl # Install Azure Python SDK (we only need the sub & the resource manager packages) + +before_script: + - ssh-keygen -q -f azure_moodle_id_rsa -N "" # Generate SSH keys to send to deployment + +script: + - npm test + - ./etc/travis.py + +notifications: + +webhooks: https://outlook.office.com/webhook/e75ee820-3466-49a6-bb0f-ecae0daf8fe1@72f988bf-86f1-41af-91ab-2d7cd011db47/TravisCI/00abc9d7ce1044818f216da49d30d54d/a38fc8f6-fb60-4169-bde6-dd2bbf01304f diff --git a/CONTRIBUTE.md b/CONTRIBUTE.md new file mode 100644 index 00000000..0e6dd460 --- /dev/null +++ b/CONTRIBUTE.md @@ -0,0 +1,236 @@ +# Contributing to Moodle on Azure + +The TL;DR version is: + + * We are a community project + * We seek to make decisions through community consensus + * We prefer debate through gradual improvement through pull requests to endless discussion about the "perfect" solution + * We are a meritocracy, not a democracy + * We welcome all your contributions including but not limited to feature requests, bug-reports, documentation and code + +## How the project is managed + +This project welcomes contributions and suggestions. Our goal is to +work on Azure specific tooling for deploying and managing the open +source [Moodle](http://moodle.org) learning management system on +Azure. We do not work on Moodle itself here, instead we work upstream +as appropriate. + +The short version of how to contribute to this project is "just do +it". Where "it" can be defined as any valuable contribution (and to be +clear, asking questions is a valuable contribution): + + * ask questions + * provide feedback + * write or update documentation + * help new users + * recommend the project to others + * test the code and report bugs + * fix bugs and issue pull requests + * give us feedback on required features + * write and update the software + * create artwork + * translate to different languages + * anything you can see that needs doing + +Most contributions require you to agree to a Contributor License +Agreement (CLA) declaring that you have the right to, and actually do, +grant us the rights to use your contribution. For details, visit +https://cla.microsoft.com. + +When you submit a pull request, a CLA-bot will automatically determine +whether you need to provide a CLA and decorate the PR appropriately +(e.g., label, comment). Simply follow the instructions provided by the +bot. You will only need to do this once across all repos using our +CLA. + +## Decision Making + +This is a community project. Decisions are made through consensus +building. All voices are equal and we welcome input from everyone. + +That said, this is not a democracy. Consensus does not mean everyone +has to agree. It merely means that nobody is objecting *and* offering +an alternative. + +What this means, in practive, is that she who does the work makes the +decisions. We'd rather discuss how to improve imperfect code than +argue over what would make perfect code. So if you have an objection +to the way we are doing things issue a pull request. + +In the unlikely event that we cannot reach consensus through consensus +then the project maintainers (as identified by their having the admin +bit on GitHub) will make a judgetment call. But normally their +role is to guide the community to consensus action, not to make +decisions on bhalf of the community. + +## Minimum Bar for Contributions + +As the project matures we will add more thorough testing. It is expected +that all contributions pass the currently available suite of tests. If +they do not then they will be rejected. + +It is also required that contributions which add features also bring +at least basic testing of that feature. + +## Planning + +This is an open source project. We have a few mantras to ensure +efficient collaboration, these mostly boil down to ensuring good +visibility into the communities goals. These include: + + * If it didn't happen in public, it didn't happen + * Scratch your own itch + +### If it didn't happen in public, it didn't happen (aka full transparency) + +The goal of this mantra is to ensure maximum visibility into our +communities work in order to: + + 1. Provide an opportunity for community feedback in order to ensure + our plans are good + 2. Provide a clear indication of what will be done, what may be done + and what won't be done + +Both of these goals lead to the second mantra "Scratch your own itch". + +### Scratch your own itch (aka getting what you want) + +This is an open source project. We welcome feature requests and, as a +community, we will provide feedback on whether we intend to work on it +or not. To this end we categories feature requests in one of 4 ways: + + * Priority 0 (will address) + * Priority 1 (may address) + * Priority 2 (maybe one day) + * wontfix (out of scope) + +Using these priorities it is easy for community members to decide +where to spend their time. For example: + + * Priority 0 items are actively being worked on by at least one + community member. Others are welcome to contribute as appropriate + (reviews are particularly important) + * Priority 1 items are seen as important and are likely to be worked + on in the short to medium term, but there is no community member + active on the project at this time. Community members are welcome + to take ownership of these issues and propose a solution that they + intend to implement. If the community accepts the proposal then it + will become a Priority 0 issue. + * Priority 2 items are seen as interesting proposals that are not in + conflict with the projects goals but are unlikely to be worked on + by any existing communty members. Community members who have a + need for these items are strongly encouraged to identify + themselves and offer a proposal for a solution. If there is enough + support within the existing community this item can become a + Priority 0 under your leadership. + * Wontfix items are considered out of scope for this project. + Community members should seek to solve the problem in different + ways. Often this will mean contribution to Moodle itself or a + plugin that is external to this community. + +## Community roles + +This section outlines roles and responsibilities within the community. + +### Users + +Users self-identify by using our software and documentation. Their +responsibilities are to benefit from our work, but we welcome +contributions from users, such as: + + * Ask questions + * Answer questions + * Feature requests + * Bug reports + * Design reviews + * Planning reviews + * Evangelize the project + * and more... + +Some users will become more involved with the project, those users +become Contributors. + +### Contributors + +Contributes self-identify by making longer term commitments to our +project. Their responsibilities are to help the project be succesful +by ensuring that our work matches the needs of our users. +Possible contributions can include: + + * Everything a User might contribute + * Remove blocks for users + * Provide design input + * Review pull requests + * Implement features + * Triage questions, feature requests and bug reports + * and more... + +Some contributors will become very engaged and therefore become an +essential part of the community, these contributors will become +Maintainers. + +### Maintainers + +We are fans of efficient processes. Maintainers are people who insert +themselves into our process to ensure they run well. The goal is to +empower our contributors who in turn focus on delighting our users. +Maintainers contributions may include: + + * Everyting Users and Contributors do + * Merge pull requests where appropriate + * Seek community consensus where conflict occurs + * Remove blocks for contributors + * and more... + +## Pull requests, Review and Merges + +We like efficient processes. Anyone is welcome to issue pull requests. +Everyone is encouraged to review pull requests. Maintainers are +responsible for merging pull requests but they are not responsible for +reviews, that is a community wide responsibility. + +We operate under two models of review process as appropriate to each +circumstance: + + * Merge then Review (our preferred model) + * Review then Merge + +### Merge Then Review + +In the "merge then review" model a maintainer will merge the pull +request into with minimal review. Community members are still expected +to review the code, but it is done after the fact. + +The goal is to get the code into a shared repository as early as +possible. This allows people, including advanced users, to start +testing it. This ensures we have the maximum possible exposure to +testing in real scenarios early in the process. Encouragin bug reports +from the whole community ensures we have visibility into breaks as +early as possible. + +This model has its risks, however. If a PR is on the critical path or +it is controversial in some way it is expected that maintainers will +ensure it recieves a thorough review before merging (see next section +on "Review then Merge". This decision is at the discretion of the +maintainer who first triages the pull request. + +Should a mistake be made and a bad merge be performed then it can +often be easier and faster to fix it under the "Merge then Review" +model than it is to provide feedback to the original author and await +a fix from them. Should the mistake have a high impact and/or no easy +fix is available we simply roll back the merge and provide feedback +via the review process. + +It should be noted that this model means that maintainers have the +right to simply merge their own code and expect others to review it +*after*. Maintainers are expected to use their best judgement when +excercising this priviledge. + +### Review Then Merge + +Where a change is on the critical path or it is potentiall +contriversial maintainers should request reviews using the GitHub +tooling. The last reviewer to sign-off on the pull request will merge +the pull request. + diff --git a/Gruntfile.js b/Gruntfile.js new file mode 100644 index 00000000..0ada1d64 --- /dev/null +++ b/Gruntfile.js @@ -0,0 +1,14 @@ +var grunt = require('grunt'); +require('load-grunt-tasks')(grunt); + +var templates = ['nested/*.json', 'managedApplication/*.json', 'loadtest/*.json', '*.json']; + +grunt.initConfig({ + jshint: { + files: templates, + options: { + jshintrc: '.jshintrc' + } + } +}); +grunt.registerTask('test', ['jshint']); diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..b17b032a --- /dev/null +++ b/LICENSE @@ -0,0 +1,17 @@ +The MIT License (MIT) +Copyright (c) Microsoft Corporation + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +associated documentation files (the "Software"), to deal in the Software without restriction, +including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial +portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT +NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/LICENSE-DOCS b/LICENSE-DOCS new file mode 100644 index 00000000..a2c95fc1 --- /dev/null +++ b/LICENSE-DOCS @@ -0,0 +1,395 @@ +Attribution 4.0 International + +======================================================================= + +Creative Commons Corporation ("Creative Commons") is not a law firm and +does not provide legal services or legal advice. Distribution of +Creative Commons public licenses does not create a lawyer-client or +other relationship. Creative Commons makes its licenses and related +information available on an "as-is" basis. Creative Commons gives no +warranties regarding its licenses, any material licensed under their +terms and conditions, or any related information. Creative Commons +disclaims all liability for damages resulting from their use to the +fullest extent possible. + +Using Creative Commons Public Licenses + +Creative Commons public licenses provide a standard set of terms and +conditions that creators and other rights holders may use to share +original works of authorship and other material subject to copyright +and certain other rights specified in the public license below. The +following considerations are for informational purposes only, are not +exhaustive, and do not form part of our licenses. + + Considerations for licensors: Our public licenses are + intended for use by those authorized to give the public + permission to use material in ways otherwise restricted by + copyright and certain other rights. Our licenses are + irrevocable. Licensors should read and understand the terms + and conditions of the license they choose before applying it. + Licensors should also secure all rights necessary before + applying our licenses so that the public can reuse the + material as expected. Licensors should clearly mark any + material not subject to the license. This includes other CC- + licensed material, or material used under an exception or + limitation to copyright. More considerations for licensors: + wiki.creativecommons.org/Considerations_for_licensors + + Considerations for the public: By using one of our public + licenses, a licensor grants the public permission to use the + licensed material under specified terms and conditions. If + the licensor's permission is not necessary for any reason--for + example, because of any applicable exception or limitation to + copyright--then that use is not regulated by the license. Our + licenses grant only permissions under copyright and certain + other rights that a licensor has authority to grant. Use of + the licensed material may still be restricted for other + reasons, including because others have copyright or other + rights in the material. A licensor may make special requests, + such as asking that all changes be marked or described. + Although not required by our licenses, you are encouraged to + respect those requests where reasonable. More_considerations + for the public: + wiki.creativecommons.org/Considerations_for_licensees + +======================================================================= + +Creative Commons Attribution 4.0 International Public License + +By exercising the Licensed Rights (defined below), You accept and agree +to be bound by the terms and conditions of this Creative Commons +Attribution 4.0 International Public License ("Public License"). To the +extent this Public License may be interpreted as a contract, You are +granted the Licensed Rights in consideration of Your acceptance of +these terms and conditions, and the Licensor grants You such rights in +consideration of benefits the Licensor receives from making the +Licensed Material available under these terms and conditions. + + +Section 1 -- Definitions. + + a. Adapted Material means material subject to Copyright and Similar + Rights that is derived from or based upon the Licensed Material + and in which the Licensed Material is translated, altered, + arranged, transformed, or otherwise modified in a manner requiring + permission under the Copyright and Similar Rights held by the + Licensor. For purposes of this Public License, where the Licensed + Material is a musical work, performance, or sound recording, + Adapted Material is always produced where the Licensed Material is + synched in timed relation with a moving image. + + b. Adapter's License means the license You apply to Your Copyright + and Similar Rights in Your contributions to Adapted Material in + accordance with the terms and conditions of this Public License. + + c. Copyright and Similar Rights means copyright and/or similar rights + closely related to copyright including, without limitation, + performance, broadcast, sound recording, and Sui Generis Database + Rights, without regard to how the rights are labeled or + categorized. For purposes of this Public License, the rights + specified in Section 2(b)(1)-(2) are not Copyright and Similar + Rights. + + d. Effective Technological Measures means those measures that, in the + absence of proper authority, may not be circumvented under laws + fulfilling obligations under Article 11 of the WIPO Copyright + Treaty adopted on December 20, 1996, and/or similar international + agreements. + + e. Exceptions and Limitations means fair use, fair dealing, and/or + any other exception or limitation to Copyright and Similar Rights + that applies to Your use of the Licensed Material. + + f. Licensed Material means the artistic or literary work, database, + or other material to which the Licensor applied this Public + License. + + g. Licensed Rights means the rights granted to You subject to the + terms and conditions of this Public License, which are limited to + all Copyright and Similar Rights that apply to Your use of the + Licensed Material and that the Licensor has authority to license. + + h. Licensor means the individual(s) or entity(ies) granting rights + under this Public License. + + i. Share means to provide material to the public by any means or + process that requires permission under the Licensed Rights, such + as reproduction, public display, public performance, distribution, + dissemination, communication, or importation, and to make material + available to the public including in ways that members of the + public may access the material from a place and at a time + individually chosen by them. + + j. Sui Generis Database Rights means rights other than copyright + resulting from Directive 96/9/EC of the European Parliament and of + the Council of 11 March 1996 on the legal protection of databases, + as amended and/or succeeded, as well as other essentially + equivalent rights anywhere in the world. + + k. You means the individual or entity exercising the Licensed Rights + under this Public License. Your has a corresponding meaning. + + +Section 2 -- Scope. + + a. License grant. + + 1. Subject to the terms and conditions of this Public License, + the Licensor hereby grants You a worldwide, royalty-free, + non-sublicensable, non-exclusive, irrevocable license to + exercise the Licensed Rights in the Licensed Material to: + + a. reproduce and Share the Licensed Material, in whole or + in part; and + + b. produce, reproduce, and Share Adapted Material. + + 2. Exceptions and Limitations. For the avoidance of doubt, where + Exceptions and Limitations apply to Your use, this Public + License does not apply, and You do not need to comply with + its terms and conditions. + + 3. Term. The term of this Public License is specified in Section + 6(a). + + 4. Media and formats; technical modifications allowed. The + Licensor authorizes You to exercise the Licensed Rights in + all media and formats whether now known or hereafter created, + and to make technical modifications necessary to do so. The + Licensor waives and/or agrees not to assert any right or + authority to forbid You from making technical modifications + necessary to exercise the Licensed Rights, including + technical modifications necessary to circumvent Effective + Technological Measures. For purposes of this Public License, + simply making modifications authorized by this Section 2(a) + (4) never produces Adapted Material. + + 5. Downstream recipients. + + a. Offer from the Licensor -- Licensed Material. Every + recipient of the Licensed Material automatically + receives an offer from the Licensor to exercise the + Licensed Rights under the terms and conditions of this + Public License. + + b. No downstream restrictions. You may not offer or impose + any additional or different terms or conditions on, or + apply any Effective Technological Measures to, the + Licensed Material if doing so restricts exercise of the + Licensed Rights by any recipient of the Licensed + Material. + + 6. No endorsement. Nothing in this Public License constitutes or + may be construed as permission to assert or imply that You + are, or that Your use of the Licensed Material is, connected + with, or sponsored, endorsed, or granted official status by, + the Licensor or others designated to receive attribution as + provided in Section 3(a)(1)(A)(i). + + b. Other rights. + + 1. Moral rights, such as the right of integrity, are not + licensed under this Public License, nor are publicity, + privacy, and/or other similar personality rights; however, to + the extent possible, the Licensor waives and/or agrees not to + assert any such rights held by the Licensor to the limited + extent necessary to allow You to exercise the Licensed + Rights, but not otherwise. + + 2. Patent and trademark rights are not licensed under this + Public License. + + 3. To the extent possible, the Licensor waives any right to + collect royalties from You for the exercise of the Licensed + Rights, whether directly or through a collecting society + under any voluntary or waivable statutory or compulsory + licensing scheme. In all other cases the Licensor expressly + reserves any right to collect such royalties. + + +Section 3 -- License Conditions. + +Your exercise of the Licensed Rights is expressly made subject to the +following conditions. + + a. Attribution. + + 1. If You Share the Licensed Material (including in modified + form), You must: + + a. retain the following if it is supplied by the Licensor + with the Licensed Material: + + i. identification of the creator(s) of the Licensed + Material and any others designated to receive + attribution, in any reasonable manner requested by + the Licensor (including by pseudonym if + designated); + + ii. a copyright notice; + + iii. a notice that refers to this Public License; + + iv. a notice that refers to the disclaimer of + warranties; + + v. a URI or hyperlink to the Licensed Material to the + extent reasonably practicable; + + b. indicate if You modified the Licensed Material and + retain an indication of any previous modifications; and + + c. indicate the Licensed Material is licensed under this + Public License, and include the text of, or the URI or + hyperlink to, this Public License. + + 2. You may satisfy the conditions in Section 3(a)(1) in any + reasonable manner based on the medium, means, and context in + which You Share the Licensed Material. For example, it may be + reasonable to satisfy the conditions by providing a URI or + hyperlink to a resource that includes the required + information. + + 3. If requested by the Licensor, You must remove any of the + information required by Section 3(a)(1)(A) to the extent + reasonably practicable. + + 4. If You Share Adapted Material You produce, the Adapter's + License You apply must not prevent recipients of the Adapted + Material from complying with this Public License. + + +Section 4 -- Sui Generis Database Rights. + +Where the Licensed Rights include Sui Generis Database Rights that +apply to Your use of the Licensed Material: + + a. for the avoidance of doubt, Section 2(a)(1) grants You the right + to extract, reuse, reproduce, and Share all or a substantial + portion of the contents of the database; + + b. if You include all or a substantial portion of the database + contents in a database in which You have Sui Generis Database + Rights, then the database in which You have Sui Generis Database + Rights (but not its individual contents) is Adapted Material; and + + c. You must comply with the conditions in Section 3(a) if You Share + all or a substantial portion of the contents of the database. + +For the avoidance of doubt, this Section 4 supplements and does not +replace Your obligations under this Public License where the Licensed +Rights include other Copyright and Similar Rights. + + +Section 5 -- Disclaimer of Warranties and Limitation of Liability. + + a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE + EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS + AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF + ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS, + IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION, + WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR + PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS, + ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT + KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT + ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU. + + b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE + TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION, + NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT, + INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES, + COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR + USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN + ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR + DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR + IN PART, THIS LIMITATION MAY NOT APPLY TO YOU. + + c. The disclaimer of warranties and limitation of liability provided + above shall be interpreted in a manner that, to the extent + possible, most closely approximates an absolute disclaimer and + waiver of all liability. + + +Section 6 -- Term and Termination. + + a. This Public License applies for the term of the Copyright and + Similar Rights licensed here. However, if You fail to comply with + this Public License, then Your rights under this Public License + terminate automatically. + + b. Where Your right to use the Licensed Material has terminated under + Section 6(a), it reinstates: + + 1. automatically as of the date the violation is cured, provided + it is cured within 30 days of Your discovery of the + violation; or + + 2. upon express reinstatement by the Licensor. + + For the avoidance of doubt, this Section 6(b) does not affect any + right the Licensor may have to seek remedies for Your violations + of this Public License. + + c. For the avoidance of doubt, the Licensor may also offer the + Licensed Material under separate terms or conditions or stop + distributing the Licensed Material at any time; however, doing so + will not terminate this Public License. + + d. Sections 1, 5, 6, 7, and 8 survive termination of this Public + License. + + +Section 7 -- Other Terms and Conditions. + + a. The Licensor shall not be bound by any additional or different + terms or conditions communicated by You unless expressly agreed. + + b. Any arrangements, understandings, or agreements regarding the + Licensed Material not stated herein are separate from and + independent of the terms and conditions of this Public License. + + +Section 8 -- Interpretation. + + a. For the avoidance of doubt, this Public License does not, and + shall not be interpreted to, reduce, limit, restrict, or impose + conditions on any use of the Licensed Material that could lawfully + be made without permission under this Public License. + + b. To the extent possible, if any provision of this Public License is + deemed unenforceable, it shall be automatically reformed to the + minimum extent necessary to make it enforceable. If the provision + cannot be reformed, it shall be severed from this Public License + without affecting the enforceability of the remaining terms and + conditions. + + c. No term or condition of this Public License will be waived and no + failure to comply consented to unless expressly agreed to by the + Licensor. + + d. Nothing in this Public License constitutes or may be interpreted + as a limitation upon, or waiver of, any privileges and immunities + that apply to the Licensor or You, including from the legal + processes of any jurisdiction or authority. + + +======================================================================= + +Creative Commons is not a party to its public +licenses. Notwithstanding, Creative Commons may elect to apply one of +its public licenses to material it publishes and in those instances +will be considered the “Licensor.” The text of the Creative Commons +public licenses is dedicated to the public domain under the CC0 Public +Domain Dedication. Except for the limited purpose of indicating that +material is shared under a Creative Commons public license or as +otherwise permitted by the Creative Commons policies published at +creativecommons.org/policies, Creative Commons does not authorize the +use of the trademark "Creative Commons" or any other trademark or logo +of Creative Commons without its prior written consent including, +without limitation, in connection with any unauthorized modifications +to any of its public licenses or any other arrangements, +understandings, or agreements concerning use of licensed material. For +the avoidance of doubt, this paragraph does not form part of the +public licenses. + +Creative Commons may be contacted at creativecommons.org. \ No newline at end of file diff --git a/LICENSE-quickstarts b/LICENSE-quickstarts deleted file mode 100644 index a72d004e..00000000 --- a/LICENSE-quickstarts +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2016 Paulo Teixeira - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 00000000..c3e549ca --- /dev/null +++ b/README.md @@ -0,0 +1,240 @@ + +# Deploy and Manage a Scalable Moodle Cluster on Azure + +This repo 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 +[Moodle](https://moodle.com) cluster on Azure. In addition, the repo 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. + +If 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 +CLI](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. + +## Fully configurable deployment + +The following button will allow you to specify various configurations for your Moodle cluster +deployment. The number of configuration options might be overwhelming, so some pre-defined/restricted deployment options for +typical Moodle scenarios follow this. + +[![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) + +## SSH Key Requirement + +All 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. + +If 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. + +## Predefined deployment options +Below 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. + +| Deployment Type | Description | Launch | +| --- | --- | --- +| Minimal | This deployment will use NFS, Microsoft SQL, 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) +| Small to Mid-Size | Supporting up to 1000 concurrent users. This deployment will use NFS (no high availability) and MySQL (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) +|Large size deployment (with high availability)| Supporting more than 2000 concurrent users. This deployment will use Gluster (for high availability, requiring 2 VMs), MySQL (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) +| Maximum |This maximal deployment will use Gluster (for high availability, adding 2 VMs for a Gluster cluster), MySQL with highest SKU, 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) + +NOTE: Depending on the region you choose to deploy the stack in - the deployment might fail due to SKUs being hardcoded in the template where they are not available. For example, today our small-mid-size deployment option hard codes Gen-4 Azure MySQL SKUs into the template, and if a region where that is currently not available in (i.e. westus2) is used, your deployment will fail. If your deployment fails, please revert to the fully configurable template where possible and change the SKU paramater to one that exists in your region (i.e. Gen-5) or alternatively change your deployment region to one in which the SKU is available (i.e. southcentralus). + +## Stack Architecture + +This template set deploys the following infrastructure core to your Moodle instance: +- Autoscaling web frontend layer (Nginx for https termination, Varnish for caching, Apache/php or nginx/php-fpm) +- Private virtual network for frontend instances +- Controller instance running cron and handling syslog for the autoscaled site +- [Azure Load balancer](https://azure.microsoft.com/en-us/services/load-balancer/) to balance across the autoscaled instances +- [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/) +- Dual [GlusterFS](https://www.gluster.org/) nodes or NFS for high availability access to Moodle files + +This template set *optionally* configures the following additional infrastructure: +- [Azure Backup](https://azure.microsoft.com/en-us/services/backup/) for Moodle site backups +- [Azure Blob Storage](https://azure.microsoft.com/en-us/services/storage/blobs/) for ObjectFS (Moodle sitedata) +- [Azure Application Gateway](https://azure.microsoft.com/en-us/services/application-gateway/) for SSL offloading and WAF +- [Azure Redis Cache](https://azure.microsoft.com/en-us/services/cache/) instance for Moodle caching +- [Azure DDoS Protection](https://azure.microsoft.com/en-us/services/ddos-protection/) plan to secure your Moodle site from DDoS attacks +- [Azure Key Vault](https://azure.microsoft.com/en-us/services/key-vault/) for storing your CA Cert for your Moodle site +- [Azure Search](https://azure.microsoft.com/en-us/services/search/) instance or three Elasticsearch VMs for HA Global Search in Moodle +- [Apache Tika](http://tika.apache.org/) VMs for search indexing in Moodle + +![network_diagram](images/stack_diagram.png "Diagram of deployed stack") + +The template also optionally installs plugins that allow Moodle to be integrated with select Azure services (see below for details). + +## Useful Moodle plugins for integrating Moodle with Azure Services +There below is a listing of useful plugins allow Moodle to be integrated with select Azure services: +- [Object File System Plugin*](https://github.com/catalyst/moodle-tool_objectfs) for [Azure Blob Storage](https://azure.microsoft.com/en-us/services/storage/blobs/) +- [Azure Search Plugin*](https://github.com/catalyst/moodle-search_azure) for [Azure Search](https://azure.microsoft.com/en-us/services/logic-apps/) +- [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) +- [Office 365 and Azure Active Directory Plugins for Moodle*](https://github.com/Microsoft/o365-moodle) for [Azure Active Directory](https://azure.microsoft.com/en-us/services/active-directory/) +- [Elasticsearch Plugin*](https://github.com/catalyst/moodle-search_elastic) + +At 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. + +## Moodle as a Managed Application +You 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. + +## Observations about the current template +The 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: + +**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). + +**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). + +**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). + +**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. At this stage Azure Database for MySQL and 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. + +**File Storage** There are two options for file storage (moodledata) - Gluster FS and NFS. The Gluster FS solution is replicated thus provides highler availability, but incurs additional cost (2 x VMs) and some performance penalties (we are exploring ways to improve this and would welcome contributions from people who know Moodle and/or Gluster). NFS is highly performant and utilizes an existing VM in the cluster (so lower cost), but it is a single point of failure. At the time of writing there is no simple way to switch from one to the other depending on expected workloads and availability requirements, again this is something we would love to see resolved. + +**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. + +**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 ;-) + +**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. + +## Common questions about this Template +1. **Is this template Moodle as IaaS or PaaS?** While the current template leverages PaaS services such as Redis, MySQL, 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/)). + +2. **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. + +3. **What configuration do you recommend for my Moodle site?** The answer is it depends. At this stage we provide some rudimenatary 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.). + +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 as much information about your deployment as possible such as: + + * average number of concurrent users your site will see + * maximum level of concurrent/simultaenous users your site needs to support + * whether or not HA is needed + * any other attributes specific to your deployment (i.e. load balancing across regions etc.) + +4. **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). + +5. **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. + +6. **How does this template relate to this [Azure Quickstart Template for Moodle](https://github.com/Azure/azure-quickstart-templates/tree/master/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. + +7. **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. + +8. **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. + +9. **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. + +10. **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. + +11. **Why is the database on a public subnet?** At this stage Azure Database for MySQL and 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. + +12. **How can I help with this effort?** Please see below. + +## Automated Testing (Travis CI) +This repository uses [Travis CI](https://travis-ci.org/) to deliver automated testing. + +The following tests are carried out for every Pull Request and will also run in a Travis CI enabled forked repository: +* **JSON Linting** - All JSON files are linted to ensure they do not contain any syntax errors. +* **JSON Code Style** - All JSON files are tested to ensure they comply with project code style rules. + +The following tests are carried out as part of the Pull Request merging prior to a contribution being accepted into the release branch: +* **Template Validation** - The template is subbmitted to Azure to ensure it is correclty formatted and contains valid logic. +* **Template Build** - The template is submitted to Azure and the stack described in the template is built to ensure a stack is correctly deployed. + +### Setting Up Travis CI for Template Build +The following describes the process required if you want to run the template validation and build steps using your own Travis and Azure accounts. + +To set up the build process, you will need: +* An Azure account or active subscription +* A fork of this repository linked to Travis CI +* Access to an installed instance of the Azure CLI +* A SSH keypair + +The 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 + +Before 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?view=azure-cli-latest + +When a Service Principal is created using the Azure CLI a JSON response is returned containing: +* **name** - This is the Service Principal username. +* **password** - This is the Service Principal password. +* **tenantId** - This is the Service Principal tenant unique ID. + +You will need these three above values to have Travis and Azure deploy and test your template. + +The next step is to take the above values returned by the Service Principal creation and use them to define *environment variables* in Travis CI. + +The 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 Using this documention set up the following three *hidden* environment variables in Travis CI for your fork of this repository. + +* **SPNAME** - The value of the *name* parameter returned by the Service Principal create proccess. +* **SPPASSWORD** - The value of the *password* parameter returned by the Service Principal create proccess. +* **SPTENANT** - The value of the *tenant* parameter returned by the Service Principal create proccess. +* **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. +* **LOCATION** *(default: southcentralus)*- Location for the test resource group. +* **RESOURCEGROUP** *(default: azmdl-travis-XXX)*- Name to use for the resource group. +* **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. + +**NOTE:** You can trigger a full CI test by adding *[full ci]* or *[fullci]* anywhere in the commit message. + +**NOTE:** Make sure you set the environment variables to hidden otherwise they will be exposed publically at run time. + +**NOTE:** As per the Travis CI documentation make sure you have correctly escaped the enviroment variable values when they are defined. + +Once the environment variables are defined, Travis CI will run the template validate and build steps as part of the test process. + +## Contributing + +This project welcomes contributions and suggestions. Our goal is to +work on Azure specific tooling for deploying and managing the open +source [Moodle](http://moodle.org) learning management system on +Azure. We do not work on Moodle itself here, instead we work upstream +as appropriate. + +The short version of how to contribute to this project is "just do +it". Where "it" can be defined as any valuable contribution (and to be +clear, asking questions is a valuable contribution): + + * ask questions + * provide feedback + * write or update documentation + * help new users + * recommend the project to others + * test the code and report bugs + * fix bugs and issue pull requests + * give us feedback on required features + * write and update the software + * create artwork + * translate to different languages + * anything you can see that needs doing + +For a more detailed discussion of how to contribute see our [Contribution Guide](CONTRIBUTE.md). + +## Code of Conduct + +This project has adopted the [Microsoft Open Source Code of +Conduct](https://opensource.microsoft.com/codeofconduct/). For more +information see the [Code of Conduct +FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact +[opencode@microsoft.com](mailto:opencode@microsoft.com) with any +additional questions or comments. + +## Legal Notices + +Microsoft and any contributors grant you a license to the Microsoft +documentation and other content in this repository under the [Creative +Commons Attribution 4.0 International Public +License](https://creativecommons.org/licenses/by/4.0/legalcode), see +the [LICENSE](LICENSE) file, and grant you a license to any code in +the repository under the [MIT +License](https://opensource.org/licenses/MIT), see the +[LICENSE-CODE](LICENSE-CODE) file. + +Microsoft, Windows, Microsoft Azure and/or other Microsoft products +and services referenced in the documentation may be either trademarks +or registered trademarks of Microsoft in the United States and/or +other countries. The licenses for this project do not grant you rights +to use any Microsoft names, logos, or trademarks. Microsoft's general +trademark guidelines can be found at +http://go.microsoft.com/fwlink/?LinkID=254653. + +Privacy information can be found at https://privacy.microsoft.com/en-us/ + +Microsoft and any contributors reserve all others rights, whether +under their respective copyrights, patents, or trademarks, whether by +implication, estoppel or otherwise. + +## Next Steps + + 1. [Deploy a Moodle Cluster](docs/Deploy.md) + 1. [Obtain Deployment Details about a Moodle Cluster](docs/Get-Install-Data.md) + 1. [Delete a Moodle Cluster](docs/Delete.md) diff --git a/README.md.quickstarts b/README.md.quickstarts deleted file mode 100644 index 8d88fd07..00000000 --- a/README.md.quickstarts +++ /dev/null @@ -1,172 +0,0 @@ -# *Autoscaling Moodle stack for Postgres or MySQL databases* - -After deploying, these templates will provide you with a new Moodle site with caching for speed and scaling frontends to handle PHP load. The filesystem behind it is mirrored for high availability and optionally backed up through Azure. Filesystem permissions and options have also been tuned to make Moodle more secure than a default install. - -[![Deploy to Azure](http://azuredeploy.net/deploybutton.png)](https://portal.azure.com/#create/Microsoft.Template/uri/https%3A%2F%2Fraw.githubusercontent.com%2FAzure%2Fazure-quickstart-templates%2Fmaster%2Fmoodle-scalable-cluster-ubuntu%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%2Fazure-quickstart-templates%2Fmaster%2Fmoodle-scalable-cluster-ubuntu%2Fazuredeploy.json) - -`Tags: cluster, ha, moodle, autoscale, linux, ubuntu` - -## *What this stack will give you* - -This template set deploys the following infrastructure: -- Autoscaling web frontend layer (Nginx, php-fpm, Varnish) -- Private virtual network for frontend instances -- Controller instance running cron and handling syslog for the autoscaled site -- Load balancer to balance across the autoscaled instances -- Postgres or MySQL database -- Azure Redis instance for Moodle caching -- ObjectFS in Azure blobs (Moodle sitedata) -- Three Elasticsearch VMs for search indexing in Moodle -- Dual gluster nodes for high availability access to Moodle files - -![network_diagram](images/stack_diagram.jpg "Diagram of deployed stack") - -## *Deployment steps* - -You can click the "deploy to Azure" button at the beginning of this document or alternatively perform a deploy from the command line: - -### *Command line deploys* -Once you've checked out the templates from git, you'll want to use the [Azure CLI tool](https://docs.microsoft.com/en-us/cli/azure/overview?view=azure-cli-latest) to deploy them. First off you'll want to create a group with these: - -`az group create --name --location ` - -Note that some locations in Azure might not support features or certain VM tiers. - -Next you'll want to either deploy and enter in all parameters manually: - -`az group deployment create --name moodle-autoscale --resource-group --template-file azuredeploy.json` - -Alternatively, you can configure all your variables in the "azuredeploy.parameters.json" file and run: - -`az group deployment create --name moodle-autoscale --resource-group --template-file azuredeploy.json --parameters azuredeploy.parameters.json` - -Note that `siteURL` is a special case in the `azuredeploy.parameters.json` files. If you do not define a value for `siteURL` or if you leave it as the default "www.example.org" this value will be overwritten by the template to be a calculated value for the public load balancers of your deployment. This allows you to experiment with this Moodle template without configuring a new domain name whilst still enabling Moodle to be configured with a production URL when appropriate. See the next section for instructions on retrieving the generated DNS name if necesary. - -Depending on what tiers you selected for VMs and the database you will be looking at roughly 1-2 hours for a full deploy. See below for selectable parameters. - -## *Using the created stack* - -In testing, stacks typically took between 1 and 2 hours to finish, -depending on spec. Once this is done you will receive a JSON with -outputs needed to continue setup. You can also retrieve these from the -portal or the CLI, more information below. The available parameters -are: - -- siteURL: If you provided a `siteURL` parameter when deploying this - will be set to the supplied value. Otherwise it will be the same as - the loadBalancerDNS, see below. -- loadBalancerDNS: This is the address of your load balancer. If you - provided a `siteURL` parameter when deploying you'll need to add a - DNS entry CNAMEs to this. -- moodleAdminPassword: The password for the "admin" user in your - Moodle install. -- controllerInstanceIP: This is the address of the controller. You - will need to SSH into this to make changes to your moodle code or - view logs. -- databaseDNS: This is the public DNS of your database instance. If - you wish to set up local backups or access the db directly, you'll - need to use this. -- databaseAdminUsername: The master account (not Moodle) username for - your database. -- databaseAdminPassword: The master account password for your - database. - -Once Moodle has been created, and (if necessary) with your custom -`siteURL` DNS pointing to the load balancer, you should be able to -load the `siteURL` and login with "admin" and the password supplied in -the moodleAdminPassword. - -#### Retrieving Deployment Configuration - -The outputs provided by your deployment should include everytyrhing -you need to manage your Moodle deployment. These are available in the -portal by clicking on the deployment for your resource group. They are -also available in via the Azure CLI. For example: - -Retrieve all the outputs in JSON format: - -``` -az group deployment show --resource-group $MOODLE_RG_NAME --name $MOODLE_DEPLOYMENT_NAME --out json --query *.outputs -``` - -Retrieve just the database password - -``` -az group deployment show --resource-group $MOODLE_RG_NAME --name $MOODLE_DEPLOYMENT_NAME --out tsv --query *.outputs.databaseAdminPassword.value -``` - -Assign the database password to a variable (BASH): - -``` -MOODLE_DATABASE_DNS="$(az group deployment show --resource-group $MOODLE_RG_NAME --name $MOODLE_DEPLOYMENT_NAME --out tsv --query *.outputs.databaseDNS.value)" -``` - -### *Updating Moodle code/settings* - -Your controller VM has Moodle code and data stored on /moodle. The code is stored in /moodle/html/moodle/. This is also mounted to your autoscaled frontends so all changes are instant. Depending on how large your Gluster disks are sized, it may be helpful to keep multiple older versions (/moodle/html1, /moodle/html2, etc) to roll back if needed. - -### *Getting an SQL dump* - -A daily sql dump of your database is taken at 02:22 and saved to /moodle/db-backup.sql(.gz). If your database is small enough to fit, you may be able to get a more current SQL dump of your Moodle db by dumping it to /moodle/. Otherwise, you'll want to do this remotely by connecting to the hostname shown in the database-dns output using the database-admin-username and database-admin-password. - -While Azure does not currently back up Postgres/MySQL databases, by dumping it to /moodle it is included in the Gluster VM backups should you enable Recovery Services in your parameters. - -### *Azure Recovery Services* - -If you have set azureBackupSwitch to 1 then Azure will provide VM backups of your Gluster node. This is recommended as it contains both your Moodle code and your sitedata. Restoring a backed up VM is outside the scope of this doc, but Azure's documentation on Recovery Services can be found here: https://docs.microsoft.com/en-us/azure/backup/backup-azure-vms-first-look-arm - -### *Resizing your Database* - -Note: This involves a lengthy site downtime. - -As mentioned above, Azure does not currently support resizing databases. You can, however, create a new database instance and change your config to point to that. To get a different size database you'll need to: - -1. Place your Moodle site into maintenance mode. You can do this either via the web interface or the command line on the controller VM. -2. Perform an SQL dump of your database, either to /moodle or remotely to your machine. -3. Create a new Azure database of the size you want inside your existing resource group. -4. Using the details in your /moodle/html/moodle/config.php create a new user and database matching the details in config.php. Make sure to grant all rights on the db to the user. -5. On the controller instance, change the db setting in /moodle/html/moodle/config.php to point to the new database. -6. Take Moodle site out of maintenance mode. -7. Once confirmed working, delete the previous database instance. - -How long this takes depends entirely on the size of your database and the speed of your VM tier. It will always be a large enough window to make a noticeable outage. - -### *Changing the SSL cert* - -The self-signed cert generated by the template is suitable for very basic testing, but a public website will want a real cert. After purchasing a trusted certificate, it can be copied to the following files to be ready immediately: - -- /moodle/certs/nginx.key: Your certificate's private key -- /moodle/certs/nginx.crt: Your combined signed certificate and trust chain certificate(s). - -Once replaced these changes become effective immediately. - - -## *Sizing Considerations and Limitations* - -Depending on what you're doing with Moodle, there are several considerations to make when configuring. The defaults included produce a cluster that is inexpensive but probably too low spec to use beyond single-user Moodle testing. - -It should be noted that as of the time of this writing both Postgres and MySQL databases are in preview at Azure. In the future larger DB sizes or different VM sizes will be available. The templates will allow you to select whatever size you want, but there are restrictions in place (VMs with certain storage types, disk size for database tiers, etc) that may prevent certains selections from working together. - -### *Database Sizing* - -As of the time of this writing, Azure supports "Basic" and "Standard" tiers for database instances. In addition the skuCapacityDTU defines Compute Units, and the number of those you can use is limited by database tier: - -- Basic: 50, 100 -- Standard: 100, 200, 400, 800 - -This value also limits the maximum number of connections, as defined here: https://docs.microsoft.com/en-us/azure/mysql/concepts-limits - -As the Moodle database will handle cron processes as well as the website, any public facing website with more than 10 users will likely require upgrading to 100. Once the site reaches 30+ users it will require upgrading to Standard for more compute units. This depends entirely on the individual site. As MySQL databases cannot change (or be restored to a different tier) once deployed it is a good idea to slightly overspec your database. - -Standard instances have a minimum storage requirement of 128000MB. All database storage, regardless of tier, has a hard upper limit of 1 terrabyte. After 128GB you gain additional iops for each GB, so if you're expecting a heavy amount of traffic you will want to oversize your storage. The current maximum iops with a 1TB disk is 3000. - -### *Controller instance sizing* - -The controller handles both syslog and cron duties. Depending on how big your Moodle cron runs are this may not be sufficient. If cron jobs are very delayed and cron processes are building up on the controller then an upgrade in tier is needed. - -### *Frontend instances* - -In general the frontend instances will not be the source of any bottlenecks unless they are severely undersized versus the rest of the cluster. More powerful instances will be needed should fpm processes spawn and exhaust memory during periods of heavy site load. This can also be mitigated against by increasing the number of VMs but spawning new VMs is slower (and potentially more expensive) than having that capacity already available. - -It is worth noting that the memory allowances on these instances allow for more memory than they may be able to provide with lower instance tiers. This is intentional as you can opt to run larger VMs with more memory and not require manual configuration. FPM also allows for a very large number of threads which prevents the system from failing during many small jobs. - diff --git a/azuredeploy-large-ha.json b/azuredeploy-large-ha.json new file mode 100644 index 00000000..17b5d772 --- /dev/null +++ b/azuredeploy-large-ha.json @@ -0,0 +1,64 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "_artifactsLocation": { + "type": "string", + "metadata": { + "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." + }, + "defaultValue": "https://raw.githubusercontent.com/Azure/Moodle/master/" + }, + "_artifactsLocationSasToken": { + "type": "securestring", + "metadata": { + "description": "The sasToken required to access _artifactsLocation. When the template is deployed using the accompanying scripts, a sasToken will be automatically generated." + }, + "defaultValue": "" + }, + "sshPublicKey": { + "metadata": { + "description": "ssh public key" + }, + "type": "string" + } + }, + "resources": [ + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2017-05-10", + "name": "mainTemplate", + "properties": { + "mode": "Incremental", + "parameters": { + "_artifactsLocation": { "value": "[parameters('_artifactsLocation')]" }, + "_artifactsLocationSasToken": { "value": "[parameters('_artifactsLocationSasToken')]" }, + "redisDeploySwitch": { "value": true }, + "sshPublicKey": { "value": "[parameters('sshPublicKey')]" }, + "autoscaleVmCountMax": { "value": 20 }, + "autoscaleVmSku": { "value": "Standard_DS3_v2" }, + "searchType": { "value": "elastic" }, + "mysqlPgresVcores": { "value": 16 }, + "mysqlPgresStgSizeGB": { "value": 512 }, + "fileServerType": { "value": "azurefiles" }, + "fileServerDiskSize": { "value": 1024 }, + "storageAccountType": { "value": "Premium_LRS" }, + "loadBalancerSku": { "value": "Standard" } + }, + "templateLink": { + "uri": "[concat(parameters('_artifactsLocation'), 'azuredeploy.json', parameters('_artifactsLocationSasToken'))]" + } + } + } + ], + "outputs": { + "mainTemplateOutputs": { + "type": "object", + "value": "[reference('mainTemplate').outputs]" + } + }, + "variables": { + "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).", + "documentation02": "For the expected small-to-mid size workloads, other parameters are fixed in this tempalte and overriden as above." + } +} diff --git a/azuredeploy-maximal.json b/azuredeploy-maximal.json new file mode 100644 index 00000000..a022c2f9 --- /dev/null +++ b/azuredeploy-maximal.json @@ -0,0 +1,63 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "_artifactsLocation": { + "type": "string", + "metadata": { + "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." + }, + "defaultValue": "https://raw.githubusercontent.com/Azure/Moodle/master/" + }, + "_artifactsLocationSasToken": { + "type": "securestring", + "metadata": { + "description": "The sasToken required to access _artifactsLocation. When the template is deployed using the accompanying scripts, a sasToken will be automatically generated." + }, + "defaultValue": "" + }, + "sshPublicKey": { + "metadata": { + "description": "ssh public key" + }, + "type": "string" + } + }, + "resources": [ + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2017-05-10", + "name": "mainTemplate", + "properties": { + "mode": "Incremental", + "parameters": { + "_artifactsLocation": { "value": "[parameters('_artifactsLocation')]" }, + "_artifactsLocationSasToken": { "value": "[parameters('_artifactsLocationSasToken')]" }, + "redisDeploySwitch": { "value": true }, + "azureBackupSwitch": { "value": true }, + "searchType": { "value": "elastic" }, + "sshPublicKey": { "value": "[parameters('sshPublicKey')]" }, + "mysqlPgresVcores": { "value": 16 }, + "mysqlPgresStgSizeGB": { "value": 512 }, + "fileServerType": { "value": "azurefiles" }, + "fileServerDiskSize": { "value": 1024 }, + "storageAccountType": { "value": "Premium_LRS" }, + "autoscaleVmSku": { "value": "Standard_DS3_v2" } + }, + "templateLink": { + "uri": "[concat(parameters('_artifactsLocation'), 'azuredeploy.json', parameters('_artifactsLocationSasToken'))]" + } + } + } + ], + "outputs": { + "mainTemplateOutputs": { + "type": "object", + "value": "[reference('mainTemplate').outputs]" + } + }, + "variables": { + "documentation01": "This wrapper template calls the main-template with maximum configs and the only required parameter (sshPublicKey).", + "documentation02": "For the best-possible performance, highly available, and most Moodle features, other parameters are fixed in this tempalte and overriden as above." + } +} diff --git a/azuredeploy-minimal.json b/azuredeploy-minimal.json new file mode 100644 index 00000000..8a03bec8 --- /dev/null +++ b/azuredeploy-minimal.json @@ -0,0 +1,65 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "_artifactsLocation": { + "type": "string", + "metadata": { + "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." + }, + "defaultValue": "https://raw.githubusercontent.com/Azure/Moodle/master/" + }, + "_artifactsLocationSasToken": { + "type": "securestring", + "metadata": { + "description": "The sasToken required to access _artifactsLocation. When the template is deployed using the accompanying scripts, a sasToken will be automatically generated." + }, + "defaultValue": "" + }, + "sshPublicKey": { + "metadata": { + "description": "ssh public key" + }, + "type": "string" + } + }, + "resources": [ + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2017-05-10", + "name": "mainTemplate", + "properties": { + "mode": "Incremental", + "parameters": { + "_artifactsLocation": { "value": "[parameters('_artifactsLocation')]" }, + "_artifactsLocationSasToken": { "value": "[parameters('_artifactsLocationSasToken')]" }, + "redisDeploySwitch": { "value": false }, + "sshPublicKey": { "value": "[parameters('sshPublicKey')]" }, + "autoscaleVmSku": { "value": "Standard_DS1_v2" }, + "enableAccelNwForOtherVmsSwitch": { "value": false }, + "dbServerType": { "value": "mysql" }, + "fileServerDiskCount": { "value": 2 }, + "fileServerDiskSize": { "value": 32 } + }, + "templateLink": { + "uri": "[concat(parameters('_artifactsLocation'), 'azuredeploy.json', parameters('_artifactsLocationSasToken'))]" + } + } + } + ], + "outputs": { + "mainTemplateOutputs": { + "type": "object", + "value": "[reference('mainTemplate').outputs]" + } + }, + "variables": { + "documentation01": "This wrapper template calls the main-template with bare minimum configs and the only required parameter (sshPublicKey).", + "documentation02": "To speed up deployment and consume least resources, other parameters are fixed in this tempalte and overriden as follows:", + "documentation03": " - fileServerType: nfs", + "documentation04": " - autoscaleVmSku: Standard_DS1_vs", + "documentation05": " - fileServerDiskCount: 2", + "documentation06": " - dbServerType: mysql", + "documentation07": " - redisDeploySwitch: false" + } +} diff --git a/azuredeploy-small2mid-noha.json b/azuredeploy-small2mid-noha.json new file mode 100644 index 00000000..5f8202f8 --- /dev/null +++ b/azuredeploy-small2mid-noha.json @@ -0,0 +1,58 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "_artifactsLocation": { + "type": "string", + "metadata": { + "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." + }, + "defaultValue": "https://raw.githubusercontent.com/Azure/Moodle/master/" + }, + "_artifactsLocationSasToken": { + "type": "securestring", + "metadata": { + "description": "The sasToken required to access _artifactsLocation. When the template is deployed using the accompanying scripts, a sasToken will be automatically generated." + }, + "defaultValue": "" + }, + "sshPublicKey": { + "metadata": { + "description": "ssh public key" + }, + "type": "string" + } + }, + "resources": [ + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2017-05-10", + "name": "mainTemplate", + "properties": { + "mode": "Incremental", + "parameters": { + "_artifactsLocation": { "value": "[parameters('_artifactsLocation')]" }, + "_artifactsLocationSasToken": { "value": "[parameters('_artifactsLocationSasToken')]" }, + "redisDeploySwitch": { "value": false }, + "sshPublicKey": { "value": "[parameters('sshPublicKey')]" }, + "mysqlPgresVcores": { "value": 8 }, + "mysqlPgresStgSizeGB": { "value": 128 }, + "fileServerDiskSize": { "value": 128 } + }, + "templateLink": { + "uri": "[concat(parameters('_artifactsLocation'), 'azuredeploy.json', parameters('_artifactsLocationSasToken'))]" + } + } + } + ], + "outputs": { + "mainTemplateOutputs": { + "type": "object", + "value": "[reference('mainTemplate').outputs]" + } + }, + "variables": { + "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).", + "documentation02": "For the expected small-to-mid size workloads, other parameters are fixed in this tempalte and overriden as above." + } +} diff --git a/azuredeploy.json b/azuredeploy.json index 78797e82..446bf495 100644 --- a/azuredeploy.json +++ b/azuredeploy.json @@ -2,271 +2,618 @@ "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", "contentVersion": "1.0.0.0", "parameters": { + "_artifactsLocation": { + "type": "string", + "metadata": { + "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." + }, + "defaultValue": "https://raw.githubusercontent.com/Azure/Moodle/master/" + }, + "_artifactsLocationSasToken": { + "type": "securestring", + "metadata": { + "description": "The sasToken required to access _artifactsLocation. When the template is deployed using the accompanying scripts, a sasToken will be automatically generated." + }, + "defaultValue": "" + }, "applyScriptsSwitch": { - "allowedValues": [ - 0, - 1 - ], - "defaultValue": 1, + "defaultValue": true, "metadata": { "description": "Switch to process or bypass all scripts/extensions" }, - "type": "int" + "type": "bool" }, "azureBackupSwitch": { - "allowedValues": [ - 0, - 1 - ], - "defaultValue": 0, + "defaultValue": false, "metadata": { "description": "Switch to configure AzureBackup and enlist VM's" }, - "type": "int" + "type": "bool" }, - "blobStorageAccountType": { - "defaultValue": "Standard_LRS", + "redisDeploySwitch": { + "defaultValue": false, + "metadata": { + "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)." + }, + "type": "bool" + }, + "vnetGwDeploySwitch": { + "defaultValue": false, + "metadata": { + "description": "Switch to deploy a virtual network gateway or not" + }, + "type": "bool" + }, + "installObjectFsSwitch": { + "defaultValue": false, + "metadata": { + "description": "Switch to install Moodle Object FS plugins (with Azure Blob storage)" + }, + "type": "bool" + }, + "installO365pluginsSwitch": { + "defaultValue": false, + "metadata": { + "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." + }, + "type": "bool" + }, + "installGdprPluginsSwitch": { + "defaultValue": false, + "metadata": { + "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." + }, + "type": "bool" + }, + "htmlLocalCopySwitch": { + "defaultValue": true, + "metadata": { + "description": "Switch to create a local copy of /moodle/html or not" + }, + "type": "bool" + }, + "ddosSwitch": { + "defaultValue": false, + "metadata": { + "description": "Switch to create a DDoS protection plan" + }, + "type": "bool" + }, + "enableAccelNwForCtlrVmSwitch": { + "defaultValue": false, + "metadata": { + "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." + }, + "type": "bool" + }, + "enableAccelNwForOtherVmsSwitch": { + "defaultValue": true, + "metadata": { + "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." + }, + "type": "bool" + }, + "httpsTermination": { "allowedValues": [ - "Standard_LRS", - "Standard_GRS", - "Standard_ZRS", - "Premium_LRS" + "VMSS", + "AppGw", + "None" ], + "defaultValue": "VMSS", "metadata": { - "description": "Blob Storage Account type" + "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." }, "type": "string" }, - "dbServerType": { - "defaultValue": "postgres", + "siteURL": { + "defaultValue": "www.example.org", + "metadata": { + "description": "URL for Moodle site" + }, + "type": "string" + }, + "moodleVersion": { "allowedValues": [ - "postgres", - "mysql" + "MOODLE_39_STABLE", + "MOODLE_38_STABLE" ], + "defaultValue": "MOODLE_38_STABLE", "metadata": { - "description": "Database type" + "description": "The Moodle version you want to install." + }, + "type": "string" + }, + "sshPublicKey": { + "metadata": { + "description": "ssh public key" + }, + "type": "string" + }, + "sshUsername": { + "defaultValue": "azureadmin", + "metadata": { + "description": "ssh user name" }, "type": "string" }, "controllerVmSku": { "defaultValue": "Standard_DS1_v2", "metadata": { - "description": "VM size for the controller node" + "description": "VM size for the controller VM" + }, + "type": "string" + }, + "webServerType": { + "defaultValue": "nginx", + "allowedValues": [ + "apache", + "nginx" + ], + "metadata": { + "description": "Web server type" }, "type": "string" }, "autoscaleVmSku": { "defaultValue": "Standard_DS2_v2", "metadata": { - "description": "VM size for autoscaled nodes" + "description": "VM size for autoscaled web VMs" }, "type": "string" }, - "autoscaleVmCount": { + "autoscaleVmCountMax": { "defaultValue": 10, "metadata": { - "description": "Maximum number of autoscaled nodes" + "description": "Maximum number of autoscaled web VMs" }, "type": "int" }, - "elasticVmSku": { - "defaultValue": "Standard_DS2_v2", + "autoscaleVmCountMin": { + "defaultValue": 1, "metadata": { - "description": "VM size for the elastic search nodes" + "description": "Minimum (also initial) number of autoscaled web VMs" }, - "type": "string" + "type": "int" }, - "firewallRuleName": { - "defaultValue": "open-to-the-world", + "osDiskStorageType": { + "defaultValue": "Premium_LRS", + "allowedValues": [ + "Premium_LRS", + "Standard_LRS" + ], "metadata": { - "description": "Database firewall rule name" + "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." }, "type": "string" }, - "gatewaySubnet": { + "phpVersion": { "allowedValues": [ - "GatewaySubnet" + "7.2", + "7.3", + "7.4" ], - "defaultValue": "GatewaySubnet", + "defaultValue": "7.4", "metadata": { - "description": "name for Virtual network gateway subnet" + "description": "php version" }, "type": "string" }, - "gatewayType": { + "dbServerType": { + "defaultValue": "mysql", "allowedValues": [ - "Vpn", - "ER" + "postgres", + "mysql", + "mssql" ], - "defaultValue": "Vpn", "metadata": { - "description": "Virtual network gateway type" + "description": "Database type" }, "type": "string" }, - "glusterVmSku": { - "defaultValue": "Standard_DS2_v2", + "dbLogin": { + "defaultValue": "dbadmin", "metadata": { - "description": "VM size for the gluster nodes" + "description": "Database admin username" }, "type": "string" }, - "glusterDiskSize": { - "defaultValue": 127, + "mysqlPgresVcores": { + "allowedValues": [ + 1, + 2, + 4, + 8, + 16, + 32 + ], + "defaultValue": 2, "metadata": { - "description": "Size per disk for gluster nodes" + "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." }, "type": "int" }, - "glusterDiskCount": { - "defaultValue": 4, + "mysqlPgresStgSizeGB": { + "defaultValue": 125, + "minValue": 5, + "maxValue": 1024, "metadata": { - "description": "Number of disks in raid0 per gluster node" + "description": "MySql/Postgresql storage size in GB. Minimum 5GB, increase by 1GB, up to 1TB (1024 GB)" }, "type": "int" }, - "moodleVersion": { + "mysqlPgresSkuTier": { "allowedValues": [ - "MOODLE_34_STABLE", - "MOODLE_33_STABLE", - "MOODLE_32_STABLE", - "MOODLE_31_STABLE", - "MOODLE_30_STABLE", - "MOODLE_29_STABLE" + "Basic", + "GeneralPurpose", + "MemoryOptimized" ], - "defaultValue": "MOODLE_34_STABLE", + "defaultValue": "GeneralPurpose", "metadata": { - "description": "The Moodle version you want to install." + "description": "MySql/Postgresql sku tier" }, "type": "string" }, - "dbLogin": { - "defaultValue": "dbadmin", + "mysqlPgresSkuHwFamily": { + "allowedValues": [ + "Gen4", + "Gen5" + ], + "defaultValue": "Gen5", "metadata": { - "description": "Database admin username" + "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." }, "type": "string" }, - "siteURL": { - "defaultValue": "www.example.org", + "mysqlVersion": { + "allowedValues": [ + "5.6", + "5.7" + ], + "defaultValue": "5.7", "metadata": { - "description": "URL for Moodle site" + "description": "Mysql version" }, "type": "string" }, - "skuCapacityDTU": { + "postgresVersion": { "allowedValues": [ - 50, - 100, - 200, - 400, - 800 + "9.5", + "9.6" ], - "defaultValue": 50, + "defaultValue": "9.6", "metadata": { - "description": "Postgresql database trasaction units" + "description": "Postgresql version" }, - "type": "int" + "type": "string" }, - "skuFamily": { + "sslEnforcement": { "allowedValues": [ - "SkuFamily" + "Disabled", + "Enabled" ], - "defaultValue": "SkuFamily", + "defaultValue": "Disabled", "metadata": { - "description": "Postgresql sku family" + "description": "MySql/Postgresql SSL connection" }, "type": "string" }, - "skuName": { + "mssqlDbServiceObjectiveName": { "allowedValues": [ - "PGSQLB50", - "PGSQLB100", - "PGSQLS100", - "PGSQLS200", - "PGSQLS400", - "PGSQLS800", - "MYSQLB50", - "MYSQLB100", - "MYSQLS100", - "MYSQLS200", - "MYSQLS400", - "MYSQLS800" + "S1", + "S2", + "S3", + "S4", + "S5", + "S6", + "S7", + "S9" ], - "defaultValue": "PGSQLB50", + "defaultValue": "S1", "metadata": { - "description": "Postgresql sku name" + "description": "MS SQL database service object names" }, "type": "string" }, - "skuSizeMB": { - "defaultValue": 51200, + "mssqlDbSize": { + "allowedValues": [ + "100MB", + "250MB", + "500MB", + "1GB", + "2GB", + "5GB", + "10GB", + "20GB", + "30GB", + "40GB", + "50GB", + "100GB", + "250GB", + "300GB", + "400GB", + "500GB", + "750GB", + "1024GB" + ], + "defaultValue": "250GB", "metadata": { - "description": "Postgresql sku size in mb" + "description": "MS SQL database size" }, - "type": "int" + "type": "string" }, - "skuTier": { + "mssqlDbEdition": { "allowedValues": [ "Basic", "Standard" ], - "defaultValue": "Basic", + "defaultValue": "Standard", "metadata": { - "description": "Postgresql sku tier" + "description": "MS SQL DB edition" }, "type": "string" }, - "sshPublicKey": { + "mssqlVersion": { + "allowedValues": [ + "12.0" + ], + "defaultValue": "12.0", "metadata": { - "description": "ssh public key" + "description": "Mssql version" }, "type": "string" }, - "sshUsername": { - "defaultValue": "azureadmin", + "fileServerType": { + "defaultValue": "nfs", + "allowedValues": [ + "gluster", + "nfs", + "nfs-ha", + "nfs-byo", + "azurefiles" + ], "metadata": { - "description": "ssh user name" + "description": "File server type: GlusterFS, NFS, and NFS-HA (2-VM highly available NFS cluster)" }, "type": "string" }, - "sslEnforcement": { + "nfsByoIpExportPath": { + "defaultValue": "", + "metadata": { + "description": "IP address and export path of the BYO-NFS share when fileServerType == nfs-byo. E.g., 172.16.1.8:/msazure" + }, + "type": "string" + }, + "fileServerDiskSize": { + "defaultValue": 127, + "metadata": { + "description": "Size per disk for gluster nodes or nfs server" + }, + "type": "int" + }, + "fileServerDiskCount": { + "defaultValue": 4, + "minValue": 2, + "maxValue": 8, + "metadata": { + "description": "Number of disks in raid0 per gluster node or nfs server" + }, + "type": "int" + }, + "fileServerVmSku": { + "defaultValue": "Standard_DS2_v2", + "metadata": { + "description": "VM size for the gluster or NFS-HA nodes" + }, + "type": "string" + }, + "keyVaultResourceId": { + "defaultValue": "", + "metadata": { + "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." + }, + "type": "string" + }, + "sslCertKeyVaultURL": { + "defaultValue": "", + "metadata": { + "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." + }, + "type": "string" + }, + "sslCertThumbprint": { + "defaultValue": "", + "metadata": { + "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." + }, + "type": "string" + }, + "caCertKeyVaultURL": { + "defaultValue": "", + "metadata": { + "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." + }, + "type": "string" + }, + "caCertThumbprint": { + "defaultValue": "", + "metadata": { + "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." + }, + "type": "string" + }, + "appGwSslCertKeyVaultResourceId": { + "defaultValue": "", + "metadata": { + "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." + }, + "type": "string" + }, + "appGwSslCertKeyVaultSecretName": { + "defaultValue": "", + "metadata": { + "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." + }, + "type": "string" + }, + "appGwSkuName": { + "defaultValue": "Standard_v2", "allowedValues": [ - "Disabled", - "Enabled" + "Standard_Small", + "Standard_Medium", + "Standard_Large", + "Standard_v2", + "WAF_Medium", + "WAF_Large", + "WAF_v2" ], - "defaultValue": "Disabled", "metadata": { - "description": "Postgresql SSL connection" + "description": "(App Gateway https termination only) Name of the Applicate Gateway SKU" }, "type": "string" }, - "postgresVersion": { + "appGwSkuTier": { + "defaultValue": "Standard_v2", "allowedValues": [ - "9.5", - "9.6" + "Standard", + "Standard_v2", + "WAF", + "WAF_v2" ], - "defaultValue": "9.6", "metadata": { - "description": "Postgresql version" + "description": "(App Gateway https termination only) Tier of the Applicate Gateway" }, "type": "string" }, - "mysqlVersion": { + "appGwSkuCapacity": { + "defaultValue": 2, + "maxValue": 10, + "minValue": 2, + "metadata": { + "description": "(App Gateway https termination only) Capacity instance count) of the Applicate Gateway" + }, + "type": "int" + }, + "storageAccountType": { + "defaultValue": "Standard_LRS", "allowedValues": [ - "5.6", - "5.7" + "Standard_LRS", + "Standard_GRS", + "Standard_ZRS", + "Premium_LRS" ], - "defaultValue": "5.7", "metadata": { - "description": "Mysql version" + "description": "Storage Account type. This storage account is only for the Moodle ObjectFS plugin and/or the (currently disabled) Azure Files file share option" + }, + "type": "string" + }, + "searchType": { + "defaultValue": "none", + "allowedValues": [ + "none", + "azure", + "elastic" + ], + "metadata": { + "description": "options of moodle global search" + }, + "type": "string" + }, + "tikaService": { + "defaultValue": "none", + "allowedValues": [ + "none", + "tika" + ], + "metadata": { + "description": "options of enabling tika service for file searching in moodle" + }, + "type": "string" + }, + "azureSearchSku": { + "defaultValue": "basic", + "allowedValues": [ + "free", + "basic", + "standard", + "standard2", + "standard3" + ], + "metadata": { + "description": "the search service level you want to create." + }, + "type": "string" + }, + "azureSearchReplicaCount": { + "defaultValue": 3, + "minValue": 1, + "maxValue": 12, + "metadata": { + "description": "Replicas distribute search workloads across the service. You need 2 or more to support high availability (applies to Basic and Standard only)." + }, + "type": "int" + }, + "azureSearchPartitionCount": { + "defaultValue": 1, + "allowedValues": [ + 1, + 2, + 3, + 4, + 6, + 12 + ], + "metadata": { + "description": "Partitions allow for scaling of document count as well as faster indexing by sharding your index over multiple Azure Search units." + }, + "type": "int" + }, + "azureSearchHostingMode": { + "defaultValue": "default", + "allowedValues": [ + "default", + "highDensity" + ], + "metadata": { + "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." + }, + "type": "string" + }, + "elasticVmSku": { + "defaultValue": "Standard_DS2_v2", + "metadata": { + "description": "VM size for the elastic search nodes" + }, + "type": "string" + }, + "tikaVmSku": { + "defaultValue": "Standard_DS2_v2", + "metadata": { + "description": "VM size for the tika search nodes" + }, + "type": "string" + }, + "customVnetId": { + "defaultValue": "", + "metadata": { + "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." }, "type": "string" }, "vNetAddressSpace": { "defaultValue": "172.31.0.0", "metadata": { - "description": "Address range for the Moodle virtual network - presumed /16 - further subneting during vnet creation" + "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)." + }, + "type": "string" + }, + "gatewayType": { + "allowedValues": [ + "Vpn", + "ER" + ], + "defaultValue": "Vpn", + "metadata": { + "description": "Virtual network gateway type" }, "type": "string" }, @@ -280,27 +627,109 @@ "description": "Virtual network gateway vpn type" }, "type": "string" + }, + "loadBalancerSku": { + "defaultValue": "Basic", + "allowedValues": [ + "Basic", + "Standard" + ], + "metadata": { + "description": "Loadbalancer SKU" + }, + "type": "string" + }, + "ubuntuVersion": { + "type": "string", + "allowedValues": [ + "16.04-LTS", + "18.04-LTS" + ], + "defaultValue": "18.04-LTS" + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Azure Location for all resources." + } } }, "resources": [ { - "apiVersion": "2015-01-01", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2017-05-10", + "name": "pid-738e3eec-68d4-4667-8377-c05c77c21f1b", + "properties": { + "mode": "Incremental", + "template": { + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "resources": [] + } + } + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2017-05-10", + "name": "networkTemplate", + "properties": { + "mode": "Incremental", + "parameters": { + "moodleCommon": { + "value": "[variables('moodleCommon')]" + } + }, + "templateLink": { + "uri": "[concat(variables('moodleCommon').baseTemplateUrl,'network.json',parameters('_artifactsLocationSasToken'))]" + } + } + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2017-05-10", + "name": "storageAccountTemplate", + "properties": { + "mode": "Incremental", + "parameters": { + "moodleCommon": { + "value": "[variables('moodleCommon')]" + } + }, + "templateLink": { + "uri": "[concat(variables('moodleCommon').baseTemplateUrl,'storageAccount.json',parameters('_artifactsLocationSasToken'))]" + } + } + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2017-05-10", + "dependsOn": [ + "Microsoft.Resources/deployments/networkTemplate" + ], "name": "dbTemplate", "properties": { "mode": "Incremental", "parameters": { "moodleCommon": { "value": "[variables('moodleCommon')]" + }, + "lbPubIp": { + "value": "[reference('networkTemplate').outputs.lbPubIp.value]" + }, + "ctlrPubIp": { + "value": "[reference('networkTemplate').outputs.ctlrPubIp.value]" } }, "templateLink": { - "uri": "[concat(variables('moodleCommon').baseTemplateUrl, parameters('dbServerType'), '.json')]" + "uri": "[concat(variables('moodleCommon').baseTemplateUrl, 'db-', parameters('dbServerType'), '.json', parameters('_artifactsLocationSasToken'))]" } - }, - "type": "Microsoft.Resources/deployments" + } }, { - "apiVersion": "2015-01-01", + "condition": "[parameters('azureBackupSwitch')]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2017-05-10", "name": "recoveryTemplate", "properties": { "mode": "Incremental", @@ -310,13 +739,14 @@ } }, "templateLink": { - "uri": "[concat(variables('moodleCommon').baseTemplateUrl,'recoveryservices',variables('moodleCommon').azureBackupSwitch,'.json')]" + "uri": "[concat(variables('moodleCommon').baseTemplateUrl,'recoveryservices.json',parameters('_artifactsLocationSasToken'))]" } - }, - "type": "Microsoft.Resources/deployments" + } }, { - "apiVersion": "2015-01-01", + "condition": "[parameters('redisDeploySwitch')]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2017-05-10", "name": "redisTemplate", "dependsOn": [ "Microsoft.Resources/deployments/networkTemplate" @@ -326,52 +756,68 @@ "parameters": { "moodleCommon": { "value": "[variables('moodleCommon')]" + }, + "subnetIdRedis": { + "value": "[reference('networkTemplate').outputs.subnetIdRedis.value]" } }, "templateLink": { - "uri": "[concat(variables('moodleCommon').baseTemplateUrl,'redis.json')]" + "uri": "[concat(variables('moodleCommon').baseTemplateUrl, 'redis.json', parameters('_artifactsLocationSasToken'))]" } - }, - "type": "Microsoft.Resources/deployments" + } }, { - "apiVersion": "2015-01-01", - "name": "networkTemplate", + "condition": "[not(equals(parameters('searchType'), 'none'))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2017-05-10", + "dependsOn": [ + "Microsoft.Resources/deployments/networkTemplate", + "Microsoft.Resources/deployments/recoveryTemplate" + ], + "name": "searchTemplate", "properties": { "mode": "Incremental", "parameters": { "moodleCommon": { "value": "[variables('moodleCommon')]" + }, + "subnetIdElastic": { + "value": "[reference('networkTemplate').outputs.subnetIdElastic.value]" } }, "templateLink": { - "uri": "[concat(variables('moodleCommon').baseTemplateUrl,'network.json')]" + "uri": "[concat(variables('moodleCommon').baseTemplateUrl, 'search-', parameters('searchType'), '.json', parameters('_artifactsLocationSasToken'))]" } - }, - "type": "Microsoft.Resources/deployments" + } }, { - "apiVersion": "2015-01-01", + "condition": "[and(equals(parameters('tikaService'), 'tika'), not(equals(parameters('searchType'), 'none')))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2017-05-10", "dependsOn": [ "Microsoft.Resources/deployments/networkTemplate", "Microsoft.Resources/deployments/recoveryTemplate" ], - "name": "elasticTemplate", + "name": "tikaTemplate", "properties": { "mode": "Incremental", "parameters": { "moodleCommon": { "value": "[variables('moodleCommon')]" + }, + "subnetIdTika": { + "value": "[reference('networkTemplate').outputs.subnetIdTika.value]" } }, "templateLink": { - "uri": "[concat(variables('moodleCommon').baseTemplateUrl,'elastic.json')]" + "uri": "[concat(variables('moodleCommon').baseTemplateUrl, 'tika.json', parameters('_artifactsLocationSasToken'))]" } - }, - "type": "Microsoft.Resources/deployments" + } }, { - "apiVersion": "2015-01-01", + "condition": "[equals(parameters('fileServerType'),'gluster')]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2017-05-10", "dependsOn": [ "Microsoft.Resources/deployments/networkTemplate", "Microsoft.Resources/deployments/recoveryTemplate" @@ -382,114 +828,234 @@ "parameters": { "moodleCommon": { "value": "[variables('moodleCommon')]" + }, + "subnetIdSan": { + "value": "[reference('networkTemplate').outputs.subnetIdSan.value]" } }, "templateLink": { - "uri": "[concat(variables('moodleCommon').baseTemplateUrl,'gluster.json')]" + "uri": "[concat(variables('moodleCommon').baseTemplateUrl,'gluster.json',parameters('_artifactsLocationSasToken'))]" } - }, - "type": "Microsoft.Resources/deployments" + } }, { - "apiVersion": "2015-01-01", + "condition": "[equals(parameters('fileServerType'),'nfs-ha')]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2017-05-10", "dependsOn": [ - "Microsoft.Resources/deployments/elasticTemplate", - "Microsoft.Resources/deployments/glusterTemplate", - "Microsoft.Resources/deployments/recoveryTemplate", + "Microsoft.Resources/deployments/networkTemplate", + "Microsoft.Resources/deployments/recoveryTemplate" + ], + "name": "nfsHaTemplate", + "properties": { + "mode": "Incremental", + "parameters": { + "_artifactsLocation": { + "value": "[parameters('_artifactsLocation')]" + }, + "_artifactsLocationSasToken": { + "value": "[parameters('_artifactsLocationSasToken')]" + }, + "location": { + "value": "[parameters('location')]" + }, + "subnetId": { + "value": "[reference('networkTemplate').outputs.subnetIdSan.value]" + }, + "node0IPAddr": { + "value": "[variables('moodleCommon').nfsHaNode0IP]" + }, + "node1IPAddr": { + "value": "[variables('moodleCommon').nfsHaNode1IP]" + }, + "nfsClientsIPRange": { + "value": "[variables('moodleCommon').nfsHaClientsIPRange]" + }, + "lbFrontEndIpAddr": { + "value": "[variables('moodleCommon').nfsHaLbIP]" + }, + "enableAccelNwSwitch": { + "value": "[parameters('enableAccelNwForOtherVmsSwitch')]" + }, + "vmSku": { + "value": "[variables('moodleCommon').fileServerVmSku]" + }, + "adminUserName": { + "value": "[parameters('sshUsername')]" + }, + "sshPublicKey": { + "value": "[parameters('sshPublicKey')]" + }, + "osType": { + "value": "[variables('moodleCommon').osType]" + }, + "osDiskStorageType": { + "value": "[parameters('osDiskStorageType')]" + }, + "dataDiskCountPerVM": { + "value": "[parameters('fileServerDiskCount')]" + }, + "dataDiskSizeInGB": { + "value": "[parameters('fileServerDiskSize')]" + }, + "resourcesUniqueString": { + "value": "[variables('resourceprefix')]" + } + }, + "templateLink": { + "uri": "[concat(variables('moodleCommon').baseTemplateUrl, 'nfs-ha.json', parameters('_artifactsLocationSasToken'))]" + } + } + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2017-05-10", + "dependsOn": [ + "Microsoft.Resources/deployments/networkTemplate", + "Microsoft.Resources/deployments/dbTemplate", "Microsoft.Resources/deployments/redisTemplate", - "Microsoft.Resources/deployments/blobStorageAccountTemplate" + "Microsoft.Resources/deployments/searchTemplate", + "Microsoft.Resources/deployments/storageAccountTemplate" ], - "name": "controllerTemplate", + "name": "vmSetupParamsTemplate", "properties": { "mode": "Incremental", "parameters": { "moodleCommon": { "value": "[variables('moodleCommon')]" }, - "blobStorageAccountKey": { - "value": "[reference('blobStorageAccountTemplate').outputs.blobStorageAccountKey.value]" + "dbFQDN": { + "value": "[reference('dbTemplate').outputs.dbFQDN.value]" + }, + "storageAccountName": { + "value": "[reference('storageAccountTemplate').outputs.storageAccountName.value]" }, - "redisPrimaryKey": { - "value": "[reference('redisTemplate').outputs.redisPrimaryKey.value]" + "storageAccountKey": { + "value": "[reference('storageAccountTemplate').outputs.storageAccountKey.value]" + }, + "redisKey": { + "value": "[if(parameters('redisDeploySwitch'), reference('redisTemplate').outputs.redisKey.value, 'None')]" + }, + "azureSearchKey": { + "value": "[if(equals(parameters('searchType'), 'azure'), reference('searchTemplate').outputs.azureSearchKey.value, 'None')]" } }, "templateLink": { - "uri": "[concat(variables('moodleCommon').baseTemplateUrl,'controller.json')]" + "uri": "[concat(variables('moodleCommon').baseTemplateUrl,'vmsetupparams.json',parameters('_artifactsLocationSasToken'))]" } - }, - "type": "Microsoft.Resources/deployments" + } }, { - "apiVersion": "2015-01-01", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2017-05-10", "dependsOn": [ - "Microsoft.Resources/deployments/controllerTemplate", + "Microsoft.Resources/deployments/vmSetupParamsTemplate", + "Microsoft.Resources/deployments/glusterTemplate", + "Microsoft.Resources/deployments/nfsHaTemplate", + "Microsoft.Resources/deployments/recoveryTemplate", + "Microsoft.Resources/deployments/networkTemplate", + "Microsoft.Resources/deployments/dbTemplate", "Microsoft.Resources/deployments/redisTemplate", - "Microsoft.Resources/deployments/dbTemplate" + "Microsoft.Resources/deployments/searchTemplate", + "Microsoft.Resources/deployments/storageAccountTemplate" ], - "name": "scaleSetTemplate", + "name": "controllerTemplate", "properties": { "mode": "Incremental", "parameters": { "moodleCommon": { "value": "[variables('moodleCommon')]" + }, + "subnetIdWeb": { + "value": "[reference('networkTemplate').outputs.subnetIdWeb.value]" + }, + "ctlrPubIpId": { + "value": "[reference('networkTemplate').outputs.ctlrPubIpId.value]" + }, + "vmSetupParamsObj": { + "value": "[reference('vmSetupParamsTemplate').outputs.vmSetupParamsObj.value]" } }, "templateLink": { - "uri": "[concat(variables('moodleCommon').baseTemplateUrl,'webvmss.json')]" + "uri": "[concat(variables('moodleCommon').baseTemplateUrl,'controller.json',parameters('_artifactsLocationSasToken'))]" } - }, - "type": "Microsoft.Resources/deployments" + } }, { - "apiVersion": "2015-01-01", - "name": "blobStorageAccountTemplate", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2017-05-10", + "dependsOn": [ + "Microsoft.Resources/deployments/vmSetupParamsTemplate", + "Microsoft.Resources/deployments/controllerTemplate", + "Microsoft.Resources/deployments/networkTemplate", + "Microsoft.Resources/deployments/redisTemplate", + "Microsoft.Resources/deployments/dbTemplate" + ], + "name": "scaleSetTemplate", "properties": { "mode": "Incremental", "parameters": { "moodleCommon": { "value": "[variables('moodleCommon')]" + }, + "subnetIdWeb": { + "value": "[reference('networkTemplate').outputs.subnetIdWeb.value]" + }, + "vmSetupParamsObj": { + "value": "[reference('vmSetupParamsTemplate').outputs.vmSetupParamsObj.value]" } }, "templateLink": { - "uri": "[concat(variables('moodleCommon').baseTemplateUrl,'blobStorageAccount.json')]" + "uri": "[concat(variables('moodleCommon').baseTemplateUrl,'webvmss.json',parameters('_artifactsLocationSasToken'))]" } - }, - "type": "Microsoft.Resources/deployments" + } } ], "outputs": { - "siteURL": { - "type": "string", - "value": "[variables('moodleCommon').siteURL]" - }, - "controllerInstanceIP": { - "type": "string", - "value": "[reference('controllerTemplate').outputs.controllerIP.value]" - }, - "databaseDNS": { - "type": "string", - "value": "[concat(variables('moodleCommon').dbServerType, '-', variables('moodleCommon').resourcesPrefix, '.', variables('moodleCommon').dbServerType, '.database.azure.com')]" - }, - "databaseAdminUsername": { - "type": "string", - "value": "[variables('moodleCommon').dbUsername]" - }, - "databaseAdminPassword": { - "type": "string", - "value": "[variables('moodleCommon').dbLoginPassword]" - }, - "firstFrontendVmIP": { - "type": "string", - "value": "[reference('scaleSetTemplate').outputs.webvm1IP.value]" - }, - "moodleAdminPassword": { - "type": "string", - "value": "[variables('moodleCommon').moodleAdminPass]" - }, - "loadBalancerDNS": { - "type": "string", - "value": "[variables('moodleCommon').lbDns]" - } + "siteURL": { + "type": "string", + "value": "[variables('moodleCommon').siteURL]" + }, + "controllerInstanceIP": { + "type": "string", + "value": "[reference('controllerTemplate').outputs.controllerIP.value]" + }, + "databaseDNS": { + "type": "string", + "value": "[reference('dbTemplate').outputs.dbFQDN.value]" + }, + "databaseAdminUsername": { + "type": "string", + "value": "[variables('moodleCommon').dbUsername]" + }, + "databaseAdminPassword": { + "type": "string", + "value": "[variables('moodleCommon').dbLoginPassword]" + }, + "firstFrontendVmIP": { + "type": "string", + "value": "[reference('scaleSetTemplate').outputs.webvm1IP.value]" + }, + "moodleAdminPassword": { + "type": "string", + "value": "[variables('moodleCommon').moodleAdminPass]" + }, + "moodleDbUsername": { + "type": "string", + "value": "[variables('moodleCommon').moodleDbUserAzure]" + }, + "moodleDbPassword": { + "type": "string", + "value": "[variables('moodleCommon').moodleDbPass]" + }, + "loadBalancerDNS": { + "type": "string", + "value": "[variables('moodleCommon').lbDns]" + }, + "loadBalancerSku": { + "type": "string", + "value": "[variables('moodleCommon').lbSku]" + } }, "variables": { "documentation01": "This main-template calls multiple sub-templates to create the moodle system", @@ -501,24 +1067,46 @@ "documentation07": " elastic - creates a elastic search cluster on a vm farm", "documentation08": " gluster - creates a gluster file system on a vm farm", "documentation09": " webvmss - creates a vm scale set", - "documentation10": " controller - creates a jumpbox and deploys code", + "documentation10": " controller - creates a controller VM and deploys code", "documentation11": "GlusterFS Sizing guidance", "moodleCommon": { - "baseTemplateUrl": "https://raw.githubusercontent.com/Azure/azure-quickstart-templates/master/moodle-scalable-cluster-ubuntu/nested/", - "scriptLocation": "https://raw.githubusercontent.com/Azure/azure-quickstart-templates/master/moodle-scalable-cluster-ubuntu/scripts/", - + "baseTemplateUrl": "[concat(parameters('_artifactsLocation'), 'nested/')]", + "scriptLocation": "[concat(parameters('_artifactsLocation'), 'scripts/')]", + "artifactsSasToken": "[parameters('_artifactsLocationSasToken')]", + "appGwBePoolName": "[concat('appgw-bepool-', variables('resourceprefix'))]", + "appGwName": "[concat('appgw-', variables('resourceprefix'))]", + "appGwPipName": "[concat('appgw-pubip-',variables('resourceprefix'))]", + "appGwSslCertKeyVaultResourceId": "[parameters('appGwSslCertKeyVaultResourceId')]", + "appGwSslCertKeyVaultSecretName": "[parameters('appGwSslCertKeyVaultSecretName')]", + "appGwSkuCapacity": "[parameters('appGwSkuCapacity')]", + "appGwSkuName": "[parameters('appGwSkuName')]", + "appGwSkuTier": "[parameters('appGwSkuTier')]", "applyScriptsSwitch": "[parameters('applyScriptsSwitch')]", - "autoscaleVmCount": "[parameters('autoscaleVmCount')]", + "autoscaleVmCountMax": "[parameters('autoscaleVmCountMax')]", + "autoscaleVmCountMin": "[parameters('autoscaleVmCountMin')]", "autoscaleVmSku": "[parameters('autoscaleVmSku')]", - "azureBackupSwitch": "[parameters( 'azureBackupSwitch')]", - "blobStorageAccountName": "[tolower(concat('abs',variables('resourceprefix')))]", - "blobStorageAccountType": "[parameters('blobStorageAccountType')]", - "computeApi": "2016-04-30-preview", + "azureBackupSwitch": "[parameters('azureBackupSwitch')]", + "azureSearchHostingMode": "[parameters('azureSearchHostingMode')]", + "azureSearchName": "[concat('azure-search-',variables('resourceprefix'))]", + "azureSearchNameHost": "[concat('azure-search-',variables('resourceprefix'),'.search.windows.net')]", + "azureSearchPartitionCount": "[parameters('azureSearchPartitionCount')]", + "azureSearchReplicaCount": "[parameters('azureSearchReplicaCount')]", + "azureSearchSku": "[parameters('azureSearchSku')]", + "commonFunctionsScriptUri": "[concat(parameters('_artifactsLocation'),'scripts/helper_functions.sh',parameters('_artifactsLocationSasToken'))]", "controllerVmSku": "[parameters('controllerVmSku')]", + "customVnetId": "[parameters('customVnetId')]", + "ctlrNicName": "[concat('controller-vm-nic-',variables('resourceprefix'))]", + "ctlrNsgName": "[concat('controller-nsg-',variables('resourceprefix'))]", + "ctlrPipName": "[concat('controller-pubip-',variables('resourceprefix'))]", + "ctlrVmName": "[concat('controller-vm-',variables('resourceprefix'))]", + "vmssNsgName": "[concat('vmss-nsg-',variables('resourceprefix'))]", + "ctlrVmSecrets": "[take(variables('ctlrVmSecretsArray'), if(empty(parameters('keyVaultResourceId')), 0, 1))]", "dbLogin": "[parameters('dbLogin')]", "dbLoginPassword": "[concat(substring(uniqueString(resourceGroup().id, deployment().name), 2, 11), '*7', toUpper('pfiwb'))]", "dbServerType": "[parameters('dbServerType')]", "dbUsername": "[concat(parameters('dbLogin'), '@', parameters('dbServerType'), '-', variables('resourceprefix'))]", + "ddosPlanName": "[concat('ddos-plan-',variables('resourceprefix'))]", + "ddosSwitch": "[parameters('ddosSwitch')]", "elasticVmSku": "[parameters('elasticVmSku')]", "elasticAvailabilitySetName": "[concat('elastic-avset-',variables('resourceprefix'))]", "elasticClusterName": "[concat('es-cluster-',variables('resourceprefix'))]", @@ -526,89 +1114,145 @@ "elasticNicName2": "[concat('elastic-vm-nic-02-',variables('resourceprefix'))]", "elasticNicName3": "[concat('elastic-vm-nic-03-',variables('resourceprefix'))]", "elasticScriptFilename": "install_elastic.sh", - "elasticVm1IP": "[concat( variables('octets')[0], '.', variables('octets')[1], '.', string(add(int(variables('octets')[2]),4)), '.20')]", - "elasticVm2IP": "[concat( variables('octets')[0], '.', variables('octets')[1], '.', string(add(int(variables('octets')[2]),4)), '.21')]", - "elasticVm3IP": "[concat( variables('octets')[0], '.', variables('octets')[1], '.', string(add(int(variables('octets')[2]),4)), '.22')]", + "elasticVm1IP": "[concat( variables('octets')[0], '.', variables('octets')[1], '.', string(add(int(variables('octets')[2]),5)), '.20')]", + "elasticVm2IP": "[concat( variables('octets')[0], '.', variables('octets')[1], '.', string(add(int(variables('octets')[2]),5)), '.21')]", + "elasticVm3IP": "[concat( variables('octets')[0], '.', variables('octets')[1], '.', string(add(int(variables('octets')[2]),5)), '.22')]", "elasticVmName": "[concat('elastic-vm-',variables('resourceprefix'))]", "elasticVmName1": "[concat('elastic-vm-01-',variables('resourceprefix'))]", "elasticVmName2": "[concat('elastic-vm-02-',variables('resourceprefix'))]", "elasticVmName3": "[concat('elastic-vm-03-',variables('resourceprefix'))]", + "enableAccelNwForCtlrVmSwitch": "[parameters('enableAccelNwForCtlrVmSwitch')]", + "enableAccelNwForOtherVmsSwitch": "[parameters('enableAccelNwForOtherVmsSwitch')]", "extBeName": "[concat('lb-backend-',variables('resourceprefix'))]", "extFeName": "[concat('lb-frontend-',variables('resourceprefix'))]", "extNatPool": "[concat('lb-natpool-',variables('resourceprefix'))]", - "extProbe": "[concat('lb-probe-',variables('resourceprefix'))]", - "firewallRuleName": "[parameters('firewallRuleName')]", + "extProbeHTTP": "[concat('lb-probe-http-',variables('resourceprefix'))]", + "extProbeHTTPS": "[concat('lb-probe-https-',variables('resourceprefix'))]", + "fileServerDiskCount": "[parameters('fileServerDiskCount')]", + "fileServerDiskSize": "[parameters('fileServerDiskSize')]", + "fileServerType": "[parameters('fileServerType')]", + "fileServerVmCount": 2, + "fileServerVmSku": "[parameters('fileServerVmSku')]", "gatewayName": "[concat('vnet-gateway-',variables('resourceprefix'))]", "gatewayPublicIPName": "[concat('vnet-gw-ip-',variables('resourceprefix'))]", - "gatewaySubnet": "[parameters('gatewaySubnet')]", - "gatewaySubnetPrefix": "[concat( variables('octets')[0], '.', variables('octets')[1], '.', string(add(int(variables('octets')[2]),2)))]", - "gatewaySubnetRange": "[concat( variables('octets')[0], '.', variables('octets')[1], '.', string(add(int(variables('octets')[2]),2)), '.0/24')]", "gatewayType": "[parameters('gatewayType')]", "gfsNameRoot": "[concat('gluster-vm-',variables('resourceprefix'))]", "gfxAvailabilitySetName": "[concat('gluster-avset-',variables('resourceprefix'))]", - "glusterDiskCount": "[parameters('glusterDiskCount')]", - "glusterDiskSize": "[parameters('glusterDiskSize')]", "glusterScriptFilename": "install_gluster.sh", - "glusterVmCount": 2, - "glusterVmSku": "[parameters('glusterVmSku')]", - "jboxNicName": "[concat('jumpbox-vm-nic-',variables('resourceprefix'))]", - "jboxNsgName": "[concat('jumpbox-nsg-',variables('resourceprefix'))]", - "jboxPipName": "[concat('jumpbox-pubip-',variables('resourceprefix'))]", - "jboxVmName": "[concat('jumpbox-vm-',variables('resourceprefix'))]", - "lbDns": "[concat('lb-',variables('resourceprefix'),'.',resourceGroup().location,'.cloudapp.azure.com')]", + "htmlLocalCopySwitch": "[parameters('htmlLocalCopySwitch')]", + "httpsTermination": "[parameters('httpsTermination')]", + "installGdprPluginsSwitch": "[parameters('installGdprPluginsSwitch')]", + "installO365pluginsSwitch": "[parameters('installO365pluginsSwitch')]", + "installObjectFsSwitch": "[parameters('installObjectFsSwitch')]", + "lbDns": "[concat('lb-',variables('resourceprefix'),'.',parameters('location'),'.cloudapp.azure.com')]", + "lbSku": "[parameters('loadBalancerSku')]", "lbName": "[concat('lb-',variables('resourceprefix'))]", "lbPipName": "[concat('lb-pubip-',variables('resourceprefix'))]", - "moodleAdminPass": "[concat(toUpper('xl'), substring(uniqueString(resourceGroup().id, deployment().name), 6, 7),'<,!1*8')]", + "location": "[parameters('location')]", + "moodleAdminPass": "[concat(toUpper('xl'), substring(uniqueString(resourceGroup().id, deployment().name), 6, 7),',1*8')]", "moodleDbName": "moodle", - "moodleDbPass": "[concat('9(#36^', substring(uniqueString(resourceGroup().id, deployment().name), 5, 8), toUpper('ercq'))]", + "moodleDbPass": "[concat('9#36^', substring(uniqueString(resourceGroup().id, deployment().name), 5, 8), toUpper('ercq'))]", "moodleDbUser": "moodle", "moodleDbUserAzure": "[concat('moodle', '@', parameters('dbServerType'), '-', variables('resourceprefix'))]", - "moodleInstallScriptFilename": "[concat(parameters('dbServerType'), '_', 'install_moodle.sh')]", - "moodleSetupScriptFilename": "setup_moodle.sh", + "moodleInstallScriptFilename": "install_moodle.sh", + "moodleOnAzureConfigsJsonPath": "/var/lib/cloud/instance/moodle_on_azure_configs.json", "moodleVersion": "[parameters('moodleVersion')]", + "mssqlDbServiceObjectiveName": "[parameters('mssqlDbServiceObjectiveName')]", + "mssqlDbSize": "[parameters('mssqlDbSize')]", + "mssqlDbEdition": "[parameters('mssqlDbEdition')]", + "mssqlVersion": "[parameters('mssqlVersion')]", + "mysqlPgresSkuHwFamily": "[parameters('mysqlPgresSkuHwFamily')]", + "mysqlPgresSkuName": "[concat(if(equals(parameters('mysqlPgresSkuTier'),'Basic'),'B', if(equals(parameters('mysqlPgresSkuTier'),'GeneralPurpose'),'GP', 'MO')), '_', parameters('mysqlPgresSkuHwFamily'), '_', string(parameters('mysqlPgresVcores')))]", + "mysqlPgresSkuTier": "[parameters('mysqlPgresSkuTier')]", + "mysqlPgresStgSizeGB": "[parameters('mysqlPgresStgSizeGB')]", + "mysqlPgresVcores": "[parameters('mysqlPgresVcores')]", "mysqlVersion": "[parameters('mysqlVersion')]", + "nfsByoIpExportPath": "[parameters('nfsByoIpExportPath')]", + "nfsHaClientsIPRange": "[variables('subnetWebRange')]", + "nfsHaExportPath": "/drbd/data", + "nfsHaLbIP": "[concat(variables('subnetSanPrefix'), '.100')]", + "nfsHaNode0IP": "[concat(variables('subnetSanPrefix'), '.110')]", + "nfsHaNode1IP": "[concat(variables('subnetSanPrefix'), '.120')]", + "osDiskStorageType": "[parameters('osDiskStorageType')]", "osType": { "offer": "UbuntuServer", "publisher": "Canonical", - "sku": "16.04-LTS", + "sku": "[parameters('ubuntuVersion')]", "version": "latest" }, + "phpVersion": "[parameters('phpVersion')]", "policyName": "[concat('policy-',variables('resourceprefix'))]", "postgresVersion": "[parameters('postgresVersion')]", "redisCacheName": "[concat('redis-',variables('resourceprefix'))]", + "redisDeploySwitch": "[parameters('redisDeploySwitch')]", "redisDns": "[concat('redis-',variables('resourceprefix'),'.redis.cache.windows.net')]", "resourcesPrefix": "[variables('resourceprefix')]", + "searchType": "[parameters('searchType')]", "serverName": "[concat(parameters('dbServerType'), '-',variables('resourceprefix'))]", - "siteURL": "[if(or(empty(parameters('siteURL')), equals(parameters('siteURL'), 'www.example.org')), concat('lb-',variables('resourceprefix'),'.',resourceGroup().location,'.cloudapp.azure.com'), parameters('siteURL'))]", - "skuCapacityDTU": "[parameters('skuCapacityDTU')]", - "skuFamily": "[parameters('skuFamily')]", - "skuName": "[parameters('skuName')]", - "skuSizeMB": "[parameters('skuSizeMB')]", - "skuTier": "[parameters('skuTier')]", - "sshPassword": "[concat(toUpper('ds'), substring(uniqueString(resourceGroup().id, deployment().name), 4, 9), '2@%5')]", + "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'))]", "sshPublicKey": "[parameters('sshPublicKey')]", "sshUsername": "[parameters('sshUsername')]", "sslEnforcement": "[parameters('sslEnforcement')]", + "storageAccountName": "[tolower(concat('abs',variables('resourceprefix')))]", + "storageAccountType": "[parameters('storageAccountType')]", + "subnetAppGw": "[concat('appgw-subnet-',variables('resourceprefix'))]", + "subnetAppGwPrefix": "[concat(variables('octets')[0], '.', variables('octets')[1], '.', string(add(int(variables('octets')[2]),6)))]", + "subnetAppGwRange": "[concat(variables('octets')[0], '.', variables('octets')[1], '.', string(add(int(variables('octets')[2]),6)), '.0/24')]", "subnetElastic": "[concat('elastic-subnet-',variables('resourceprefix'))]", - "subnetElasticPrefix": "[concat( variables('octets')[0], '.', variables('octets')[1], '.', string(add(int(variables('octets')[2]),4)))]", - "subnetElasticRange": "[concat( variables('octets')[0], '.', variables('octets')[1], '.', string(add(int(variables('octets')[2]),4)), '.0/24')]", + "subnetElasticPrefix": "[concat( variables('octets')[0], '.', variables('octets')[1], '.', string(add(int(variables('octets')[2]),5)))]", + "subnetElasticRange": "[concat( variables('octets')[0], '.', variables('octets')[1], '.', string(add(int(variables('octets')[2]),5)), '.0/24')]", + "subnetGateway": "GatewaySubnet", + "subnetGatewayPrefix": "[concat(variables('octets')[0], '.', variables('octets')[1], '.', string(add(int(variables('octets')[2]),4)))]", + "subnetGatewayRange": "[concat(variables('octets')[0], '.', variables('octets')[1], '.', string(add(int(variables('octets')[2]),4)), '.0/24')]", "subnetRedis": "[concat('redis-subnet-',variables('resourceprefix'))]", "subnetRedisPrefix": "[concat( variables('octets')[0], '.', variables('octets')[1], '.', string(add(int(variables('octets')[2]),3)))]", "subnetRedisRange": "[concat( variables('octets')[0], '.', variables('octets')[1], '.', string(add(int(variables('octets')[2]),3)), '.0/24')]", "subnetSan": "[concat('san-subnet-',variables('resourceprefix'))]", - "subnetSanPrefix": "[concat( variables('octets')[0], '.', variables('octets')[1], '.', string(add(int(variables('octets')[2]),1)))]", - "subnetSanRange": "[concat( variables('octets')[0], '.', variables('octets')[1], '.', string(add(int(variables('octets')[2]),1)), '.0/24')]", + "subnetSanPrefix": "[variables('subnetSanPrefix')]", + "subnetSanRange": "[concat( variables('octets')[0], '.', variables('octets')[1], '.', string(add(int(variables('octets')[2]),2)), '.0/24')]", + "subnetTika": "[concat('tika-subnet-',variables('resourceprefix'))]", + "subnetTikaPrefix": "[concat( variables('octets')[0], '.', variables('octets')[1], '.', string(add(int(variables('octets')[2]),1)))]", + "subnetTikaRange": "[concat( variables('octets')[0], '.', variables('octets')[1], '.', string(add(int(variables('octets')[2]),1)), '.0/24')]", "subnetWeb": "[concat('web-subnet-',variables('resourceprefix'))]", "subnetWebPrefix": "[concat( variables('octets')[0], '.', variables('octets')[1], '.', string(add(int(variables('octets')[2]),0)))]", - "subnetWebRange": "[concat( variables('octets')[0], '.', variables('octets')[1], '.', string(add(int(variables('octets')[2]),0)), '.0/24')]", + "subnetWebRange": "[variables('subnetWebRange')]", + "thumbprintSslCert": "[if(or(empty(parameters('keyVaultResourceId')), empty(parameters('sslCertThumbprint'))), 'None', parameters('sslCertThumbprint'))]", + "thumbprintCaCert": "[if(or(empty(parameters('keyVaultResourceId')), empty(parameters('caCertThumbprint'))), 'None', parameters('caCertThumbprint'))]", + "tikaNicName": "[concat('tika-vm-nic-',variables('resourceprefix'))]", + "tikaScriptFilename": "install_tika.sh", + "tikaService": "[parameters('tikaService')]", + "tikaVmIP": "[if(equals(parameters('tikaService'), 'tika'), concat( variables('octets')[0], '.', variables('octets')[1], '.', string(add(int(variables('octets')[2]),1)), '.20'), 'none')]", + "tikaVmName": "[concat('tika-vm-',variables('resourceprefix'))]", + "tikaVmSku": "[parameters('tikaVmSku')]", "vNetAddressSpace": "[parameters('vNetAddressSpace')]", "vaultName": "[concat('vault-',variables('resourceprefix'))]", "vmssName": "[concat('vmss-',variables('resourceprefix'))]", "vmssdStorageAccounttName": "[concat('vmss',uniqueString(resourceGroup().id))]", + "vnetGwDeploySwitch": "[parameters('vnetGwDeploySwitch')]", "vnetName": "[concat('vnet-',variables('resourceprefix'))]", - "vpnType": "[parameters('vpnType')]" + "vpnType": "[parameters('vpnType')]", + "webServerSetupScriptFilename": "setup_webserver.sh", + "webServerType": "[parameters('webServerType')]" }, + "certUrlArray": [ + { + "certificateUrl": "[parameters('sslCertKeyVaultURL')]" + }, + { + "certificateUrl": "[parameters('caCertKeyVaultURL')]" + } + ], + "ctlrVmSecretsArray": [ + { + "sourceVault": { + "id": "[parameters('keyVaultResourceId')]" + }, + "vaultCertificates": "[take(variables('certUrlArray'), if(empty(parameters('caCertKeyVaultURL')), 1, 2))]" + } + ], "octets": "[split(parameters('vNetAddressSpace'), '.')]", - "resourceprefix": "[substring(uniqueString(resourceGroup().id, deployment().name), 3, 6)]" + "resourceprefix": "[substring(uniqueString(resourceGroup().id, deployment().name), 3, 6)]", + "subnetSanPrefix": "[concat( variables('octets')[0], '.', variables('octets')[1], '.', string(add(int(variables('octets')[2]),2)))]", + "subnetWebRange": "[concat( variables('octets')[0], '.', variables('octets')[1], '.', string(add(int(variables('octets')[2]),0)), '.0/24')]" } } diff --git a/azuredeploy.parameters.json b/azuredeploy.parameters.json index 7beb4cd8..b1999f72 100644 --- a/azuredeploy.parameters.json +++ b/azuredeploy.parameters.json @@ -2,25 +2,8 @@ "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#", "contentVersion": "1.0.0.0", "parameters": { - "autoscaleVmCount": { "value": 10 }, - "autoscaleVmSku": { "value": "Standard_DS2_v2" }, - "azureBackupSwitch": { "value": 0 }, - "blobStorageAccountType": { "value": "Standard_LRS"}, - "controllerVmSku": { "value": "Standard_DS1_v2" }, - "dbServerType": { "value": "postgres" }, - "elasticVmSku": { "value": "Standard_DS2_v2" }, - "glusterDiskCount": { "value": 4 }, - "glusterDiskSize": { "value": 127 }, - "glusterVmSku": { "value": "Standard_DS2_v2" }, - "moodleVersion": { "value": "MOODLE_34_STABLE" }, - "mysqlVersion": { "value": "5.7" }, - "postgresVersion": { "value": "9.6" }, - "siteURL": { "value": "www.example.org" }, - "skuCapacityDTU": { "value": 50 }, - "skuName": { "value": "PGSQLB50" }, - "skuSizeMB": { "value": 51200 }, - "skuTier": { "value": "Basic" }, - "vNetAddressSpace": { "value": "172.31.0.0" }, - "sshPublicKey": { "value": "GEN-SSH-PUB-KEY" } + "sshPublicKey": { "value": "GEN-SSH-PUB-KEY" }, + "fileServerDiskCount": { "value": 2 }, + "fileServerDiskSize": { "value": 32 } } } diff --git a/docs/Cleanup.md b/docs/Cleanup.md new file mode 100644 index 00000000..967f65ee --- /dev/null +++ b/docs/Cleanup.md @@ -0,0 +1,22 @@ +# Cleanup All Resources + +To cleanup a Moodle deployment simply delete the Resource Group that +contains it. The commands below will iterate over your workspace +directory and delete all deployments. + +## Prerequisites + +First we need to ensure our [environment variables](./Environment-Variables.md) are correctly configured. + +## Remove each resource group + +This command will delete all resources in *all* resource groups. Run +with caution. + +Note, that this command will not fully delete the resource group if +you have Azure Backup enabled since the Recovery Services Vault will +not be deleted (it's got the backups of you data!). + +``` bash +for filename in $MOODLE_AZURE_WORKSPACE/*; do az group delete --yes --name $(basename $filename) --no-wait; done +``` diff --git a/docs/Deploy.md b/docs/Deploy.md new file mode 100644 index 00000000..56a6abb4 --- /dev/null +++ b/docs/Deploy.md @@ -0,0 +1,168 @@ +# Deploy Autoscaling Moodle Stack to Azure + +After following the steps in this this document you with awill have a +new Moodle site with caching for speed and scaling frontends to handle +load. The filesystem behind it is mirrored for high availability and +optionally backed up through Azure. Filesystem permissions and options +have also been tuned to make Moodle more secure than a default +install. + +## Prerequisites + +To make things consitent across different sessions managing Moodle we +should [configure the environment](./Preparation.md). + + +## Create Resource Group + +When you create the Moodle cluster you will create many resources. On +Azure it is a best practice to collect such resources together in a +Resource Group. The first thing we need to do, therefore, is create a +resource group: + +``` +az group create --name $MOODLE_RG_NAME --location $MOODLE_RG_LOCATION +``` + +Results: + +```expected_similarity=0.4 +{ + "id": "/subscriptions/325e7c34-99fb-4190-aa87-1df746c67705/resourceGroups/rgmoodlearm3", + "location": "westus2", + "managedBy": null, + "name": "rgmoodlearm3", + "properties": { + "provisioningState": "Succeeded" + }, + "tags": null +} +``` + +## Create Azure Deployment Parameters + +Your deployment will be configured using an +`azuredeploy.parameters.json` file. It is possible to provide these +parameters interactively via the command line by simply omitting the +paramaters file in the command in the next section. However, it is +more reproducible if we use a paramaters file. + +A good set of defaults are provided in the git repository. These +defaults create a scalable cluster that is suitable for low volume +testing. If you are building out a production service you should +review the section below on sizing considerations. For now we will +proceed with the defaults, but there is one value, the `sshPublicKey` +that **must** be provided. The following command will replace the +placeholder in the parameters template file with an SSH key used for +testing puporses (this is created as part of the envrionment setup in +the prerequisites): + +``` bash +ssh_pub_key=`cat $MOODLE_SSH_KEY_FILENAME.pub` +echo $ssh_pub_key +sed "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 +``` + +If you'd like to configure the Moodle cluster (to be deployed) +with your own SSL certificate for your domain (siteURL) at the +deployment time, you can do so by using [Azure Key Vault](https://azure.microsoft.com/en-us/services/key-vault/) +and following the instructions in the [SSL cert documentation](SslCert.md). + +For more information see the [parameters documentation](Parameters.md). + +## Deploy cluster + +Now that we have a resource group and a configuration file we can +create the cluster itself. This is done with a single command: + +``` +az group deployment 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 +``` + +## Using the created stack + +In testing, stacks typically took between 1 and 2 hours to finish, +depending on spec. Once complete you will receive a JSON output +containing information needed to manage your Moodle install (see +`outputs`). You can also retrieve this infromation from the portal or +the CLI. + +Once Moodle has been created, and (where necessary) you have +configured your custom `siteURL` DNS to point to the +`loadBalancerDNS`, you should be able to load the `siteURL` in a +browser and login with the username "admin" and the +`moodleAdminPassword`. Note that the values for each of these +parameters are avialble in the portal or the `outputs` section of the +JSON response from the previous deploy command. See [documentation on +how to retrieve configuration data](./Get-Install-Data.md) along +with full details of all the output parameters avialble to you. + +Note that by default the deployment uses a self-signed certificate, +consequently you will recieve a warning when accessing the site. To +add a genuine certificate see the documentation on [managing your +cluster](./Manage.md). + +## Sizing Considerations and Limitations + +Depending on what you're doing with Moodle you will want to configure +your deployment appropriately.The defaults included produce a cluster +that is inexpensive but probably too low spec to use beyond simple +testing scenarios. This section includes an overview of how to size +the database and VM instances for your use case. + +### Database Sizing + +As of the time of this writing, Azure supports "Basic", "General Purpose" and "Memory Optimized" +tiers for MySQL/PostgreSQL database instances. In addition the mysqlPgresVcores defines +the number of vCores for each DB server instance, and the number of those you can use is limited by +database tier: + +- Basic: 1, 2 +- General Purpose: 2, 4, 8, 16, 32 +- Memory Optimized: 2, 4, 8, 16 + +This value also limits the maximum number of connections, as defined +here: https://docs.microsoft.com/en-us/azure/mysql/concepts-limits + +As the Moodle database will handle cron processes as well as the +website, any public facing website with more than 10 users will likely +require upgrading to 100. Once the site reaches 30+ users it will +require upgrading to General Purpose for more compute units. This depends +entirely on the individual site. As MySQL databases cannot change (or +be restored to a different tier) once deployed it is a good idea to +slightly overspec your database. + +All MySQL/PostgreSQL database storage, regardless of tier, has a hard upper limit of 1 +terabyte (1024 GB), starting from 5 GB minimum, increasing by 1 GB. You gain additional iops for each added GB, so if +you're expecting a heavy amount of traffic you will want to oversize +your storage. The current maximum iops with a 1TB disk is 3000. + +### Controller instance sizing + +The controller handles both syslog and cron duties. Depending on how +big your Moodle cron runs are this may not be sufficient. If cron jobs +are very delayed and cron processes are building up on the controller +then an upgrade in tier is needed. + +### Frontend instances + +In general the frontend instances will not be the source of any +bottlenecks unless they are severely undersized versus the rest of the +cluster. More powerful instances will be needed should fpm processes +spawn and exhaust memory during periods of heavy site load. This can +also be mitigated against by increasing the number of VMs but spawning +new VMs is slower (and potentially more expensive) than having that +capacity already available. + +It is worth noting that the memory allowances on these instances allow +for more memory than they may be able to provide with lower instance +tiers. This is intentional as you can opt to run larger VMs with more +memory and not require manual configuration. FPM also allows for a +very large number of threads which prevents the system from failing +during many small jobs. + + +## Next Steps + + 1. [Retrieve configuration details using CLI](./Get-Install-Data.md) + 1. [Manage the Moodle cluster](./Manage.md) diff --git a/docs/Environment-Variables.md b/docs/Environment-Variables.md new file mode 100644 index 00000000..b0f071b7 --- /dev/null +++ b/docs/Environment-Variables.md @@ -0,0 +1,132 @@ +# Environment Variables + +In order to configure our deployment and tools we'll set up some +environment variables to ensure consistency. If you are running these +scripts through SimDem you can customize these values by copying and +editing `env.json` into `env.local.json`. + +We'll need a unique name for our Resource Group in Azure, but when +running in an automated mode it is useful to have a (mostly) unique +name for your deployment and related resources. We'll use a timestamp. +If the environmnt variable `MOODLE_RG_NAME` is not set we will +create a new value using a timestamp: + + +``` shell +if [ -z "$MOODLE_RG_NAME" ]; then MOODLE_RG_NAME=moodle_$(date +%Y-%m-%d-%H); fi +``` + +Other configurable values for our Azure deployment include the +location and depoloyment name. We'll standardize these, but you can +use different values if you like. + +``` shell +MOODLE_RG_LOCATION=southcentralus +MOODLE_DEPLOYMENT_NAME=MasterDeploy +``` + +We also need to provide an SSH key. Later we'll generate this if it +doesn't already exist but to enable us to reuse an existing key we'll +store it's filename in an Environment Variable. + +``` shell +MOODLE_SSH_KEY_FILENAME=~/.ssh/moodle_id_rsa +``` + +We need a workspace for storing configuration files and other +per-deployment artifacts: + +``` shell +MOODLE_AZURE_WORKSPACE=~/.moodle +``` + +## Create Workspace + +Ensure the workspace for this particular deployment exists: + +``` +mkdir -p $MOODLE_AZURE_WORKSPACE/$MOODLE_RG_NAME +``` + +## Validation + +After working through this file there should be a number of +environment variables defined that will be used to provide a common +setup for all our Moodle on Azure work. + +The resource group name defines the name of the group into which all +resources will be, or are, deployed. + +```bash +echo "Resource Group for deployment: $MOODLE_RG_NAME" +``` + +Results: + +``` +Resource Group for deployment: southcentralus +``` + +The resource group location is: + +```bash +echo "Deployment location: $MOODLE_RG_LOCATION" +``` + +Results: + +``` +Deployment location: southcentralus +``` + +When deploying a Moodle cluster the deployment will be given a name so +that it can be identified later should it be neceessary to debug. + + +```bash +echo "Deployment name: $MOODLE_DEPLOYMENT_NAME" +``` + +Results: + +``` +Deployment name: MasterDeploy +``` + +The SSH key to use can be found in a file, if necessary this will be +created as part of these scripts. + +``` shell +echo "SSH key filename: $MOODLE_SSH_KEY_FILENAME" +``` + +Results: + +``` +SSH key filename: ~/.ssh/moodle_id_rsa +``` + +Configuration files will be written to / read from a customer directory: + +``` shell +echo "Workspace directory: $MOODLE_AZURE_WORKSPACE" +``` + +Results: + +``` +Workspace directory: ~/.moodle +``` + +Ensure the workspace directory exists: + + +``` bash +if [ ! -f "$MOODLE_AZURE_WORKSPACE/$MOODLE_RG_NAME" ]; then echo "Worspace exists"; fi +``` + +Results: + +``` +Workspace exists +``` diff --git a/docs/Get-Install-Data.md b/docs/Get-Install-Data.md new file mode 100644 index 00000000..c4ed98b2 --- /dev/null +++ b/docs/Get-Install-Data.md @@ -0,0 +1,154 @@ +# Retrieve essential install details + +Once a deployment has completed the ARM template will output some +values that you will need for managing your Moodle instalation. These +are available in the portal, but in this document we will retrieve +them using the AZ command line tools and through the AZ CLI tool. This +document describes the available parameters and how to retrieve them. + +## Prerequisites + +In order to configure our deployment and tools we'll set up some +[environment variables](./Environment-Variables.md) to ensure consistency. + +## Output Paramater Overview + +The available output parameters are: + + - **siteURL**: If you provided a `siteURL` parameter when deploying this + will be set to the supplied value. Otherwise it will be the same as + the loadBalancerDNS, see below. + - **loadBalancerDNS**: This is the DNS name of your application load + balancer. If you provided a `siteURL` parameter when deploying + you'll need to add a DNS entry to its CNAMEs pointing to this address. + - **moodleAdminPassword**: The generated password for the "admin" user + in your Moodle install. + - **controllerInstanceIP**: This is the IP address of the controller + Virtual Machine. You will need to SSH into this to make changes to + your Moodle code or view logs. + - **databaseDNS**: This is the public DNS of your database instance. If + you wish to set up local backups or access the DB directly, you'll + need to use this. + - **databaseAdminUsername**: The admin username for your database + (this is not the same as your Moodle username). + - **databaseAdminPassword**: The admin password for your + database (this is not the same as your Moodle password). + +## Retrieving Output Parameters Using the CLI + +To get a complete list of outputs in json format use: + +```bash +az group deployment show --resource-group $MOODLE_RG_NAME --name $MOODLE_DEPLOYMENT_NAME --out json --query *.outputs +``` + +Individual outputs can be retrieved by filtering, for example, to get +just the value of the `siteURL` use: + +``` bash +az group deployment show --resource-group $MOODLE_RG_NAME --name $MOODLE_DEPLOYMENT_NAME --out json --query *.outputs.siteURL.value +``` + +However, since we are reqeusting JSON output (the default) the value +is enclosed in quotes. In order to remove these we can output as a tab +separated list (TSV): + +``` bash +az group deployment show --resource-group $MOODLE_RG_NAME --name $MOODLE_DEPLOYMENT_NAME --out tsv --query *.outputs.siteURL +``` + +Now we can assign individual values to environment variables, for example: + +``` bash +MOODLE_SITE_URL="$(az group deployment show --resource-group $MOODLE_RG_NAME --name $MOODLE_DEPLOYMENT_NAME --out tsv --query *.outputs.siteURL.value)" +``` + +### Retrieving Moodle Site URL + +The Site URL is the value used to configure Moodle's base URL. The +site URL can be provided as an input to the template via the parameter +`siteURL`, in which case you will not need to retrieve this from the +outputs. However, if you do not define this, or if you leave it as the +default "www.example.org" you will need to retrieve this value from +Azure using the following command: + +```bash +MOODLE_SITE_URL="$(az group deployment show --resource-group $MOODLE_RG_NAME --name $MOODLE_DEPLOYMENT_NAME --out tsv --query *.outputs.siteURL.value)" +``` + +#### Retrieving Moodle Site Load Balancer URL + +The load balancer DNS is the publicly registered DNS name for your +Moodle DNS. If this is different from the site URL it is important to +ensure that you configure your DNS entry for site URL to point at the +load balancer. + +```bash +MOODLE_LOAD_BALANCER_DNS="$(az group deployment show --resource-group $MOODLE_RG_NAME --name $MOODLE_DEPLOYMENT_NAME --out tsv --query *.outputs.loadBalancerDNS.value)" +``` + +### Retrieving Moodle Administrator Password + +Moodle admin password (username is "admin"): + +```bash +MOODLE_ADMIN_PASSWORD="$(az group deployment show --resource-group $MOODLE_RG_NAME --name $MOODLE_DEPLOYMENT_NAME --out tsv --query *.outputs.moodleAdminPassword.value)" +``` + +### Retriving Controller Virtual Machine Details + +The controller VM runs management tasks for the cluster, such as cron jobs and syslog. + +```bash +MOODLE_CONTROLLER_INSTANCE_IP="$(az group deployment show --resource-group $MOODLE_RG_NAME --name $MOODLE_DEPLOYMENT_NAME --out tsv --query *.outputs.controllerInstanceIP.value)" +``` + +There is no username and password for this VM since a username and SSH +key are provided as input parameters to the template. + +### Retreiving Database Information + +#### Database URL + +``` bash +MOODLE_DATABASE_DNS="$(az group deployment show --resource-group $MOODLE_RG_NAME --name $MOODLE_DEPLOYMENT_NAME --out tsv --query *.outputs.databaseDNS.value)" +``` +#### Database admin username + +``` bash +MOODLE_DATABASE_ADMIN_USERNAME="$(az group deployment show --resource-group $MOODLE_RG_NAME --name $MOODLE_DEPLOYMENT_NAME --out tsv --query *.outputs.databaseAdminUsername.value)" +``` + +#### Database admin password + +``` bash +MOODLE_DATABASE_ADMIN_PASSWORD="$(az group deployment show --resource-group $MOODLE_RG_NAME --name $MOODLE_DEPLOYMENT_NAME --out tsv --query *.outputs.databaseAdminPassword.value)" +``` + +### Retrieving Moodle Application VNET Information + +First frontend VM IP: + +``` bash +MOODLE_FIRST_FRONTEND_VM_IP="$(az group deployment show --resource-group $MOODLE_RG_NAME --name $MOODLE_DEPLOYMENT_NAME --out tsv --query *.outputs.firstFrontendVmIP.value)" +``` + +# Validation + +After having run each of the commands in this document you should have +each of the output parameters available in environment variable: + +``` bash +echo $MOODLE_SITE_URL +echo $MOODLE_LOAD_BALANCER_DNS +echo $MOODLE_ADMIN_PASSWORD +echo $MOODLE_CONTROLLER_INSTANCE_IP +echo $MOODLE_DATABASE_DNS +echo $MOODLE_DATABASE_ADMIN_USERNAME +echo $MOODLE_DATABASE_ADMIN_PASSWORD +echo $MOODLE_FIRST_FRONTEND_VM_IP +``` + +## Next Steps + + 1. [Manage the Moodle cluster](./Manage.md) diff --git a/docs/Manage.md b/docs/Manage.md new file mode 100644 index 00000000..e7f3d48a --- /dev/null +++ b/docs/Manage.md @@ -0,0 +1,196 @@ +# Managing a Scalable Moodle Cluster in Azure + +This document provides an overview of how to perform various +management tasks on a scalable Moodle cluster on Azure. + +## Prerequisites + +In order to configure our deployment and tools we'll set up some +[environment variables](./Environment-Variables.md) to ensure consistency. + +In order to manage a cluster it is clearly necessary to first [deploy +a scalable Moodle cluster on Azure](./Deploy.md). + +For convenience and readability this document also assumes that essential [deployment details for your cluster have been assigned to environment variables](./Get-Install-Data.md). + +## Updating Moodle code/settings + +Your controller Virtual Machine has Moodle code and data stored in +`/moodle`. The site code is stored in `/moodle/html/moodle/`. This +data is replicated across dual gluster nodes to provide high +availability. This directory is also mounted to your autoscaled +frontends so all changes to files on the controller VM are immediately +available to all frontend machines (when the `htmlLocalCopySwitch` in `azuredeploy.json` +is false--otherwise, see below). Note that any updates on Moodle code/settings +(e.g., additional plugin installations, Moodle version upgrade) have to be done +on the controller VM using shell commands, not through a web browser, because the +HTML directory's permission is read-only for the web frontend VMs (thus any web-based +Moodle code updates will fail). + +Depending on how large your Gluster disks are sized, it may be helpful +to keep multiple older versions (/moodle/html1, /moodle/html2, etc) to +roll back if needed. + +To connect to your Controller VM use SSH with a username of +'azureuser' and the SSH provided in the `sshPublicKey` input +parameter. For example, to retrieve a listing of files and directories +in the `/moodle` directory use: + +``` +ssh -o StrictHostKeyChecking=no azureadmin@$MOODLE_CONTROLLER_INSTANCE_IP ls -l /moodle +``` + +Results: + +``` +Warning: Permanently added '52.228.45.38' (ECDSA) to the list of known hosts. +total 12 +drwxr-xr-x 2 www-data www-data 4096 Jan 17 00:59 certs +-rw-r--r-- 1 root root 0 Jan 17 02:22 db-backup.sql +drwxr-xr-x 3 www-data www-data 4096 Jan 17 00:54 html +drwxrwx--- 10 www-data www-data 4096 Jan 17 06:55 moodledata +``` + +**IMPORTANT NOTE** + +It is important to realize that the `-o StrictHostKeyChecking=no` +option in the above SSH command presents a security risk. It is +included here to facilitate automated validation of these commands. It +is not recommended to use this option in production environments, +instead run the command manually and validate the host key. +Subsequent executions of an SSH command will not require this +validation step. For more information there is an excellent +[superuser.com +Q&A](https://superuser.com/questions/421074/ssh-the-authenticity-of-host-host-cant-be-established/421084#421084). + +### If you set `htmlLocalCopySwitch` to true (this is the default now) + +Originally the `/moodle/html` directory was shared across all autoscaled +web VMs through the specified file server (Gluster or NFS), and this is +not good for web response time. Therefore, we introduced the +`htmlLocalCopySwitch` that'll copy the `/moodle/html` directory to +`/var/www/html` in each autoscaled web VM and reconfigures the web +server (apache/nginx)'s server root directory accordingly, when it's set +to true. This now requires directory sync between `/moodle/html` and +`/var/www/html`, and currently it's addressed by simple polling +(minutely). Therefore, if you are going to update your Moodle +code/settings with the switch set to true, please follow the +following steps: + +* Put your Moodle site to maintenance mode. + * This will need to be done on the contoller VM with some shell command. + * It should be followed by running the following command to propagate the change to all autoscaled web VMs: + ```bash + $ sudo /usr/local/bin/update_last_modified_time.moodle_on_azure.sh + ``` + * Once this command is executed, each autoscaled web VM will pick up (sync) the changes within 1 minute, so wait for one minute. +* 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. +* 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). + +Please do let us know on this Github repo's Issues if you encounter any problems with this process. + +## Getting an SQL dump + +By default a daily sql dump of your database is taken at 02:22 and +saved to `/moodle/db-backup.sql`(.gz). This file can be retrieved +using SCP or similar. For example: + +``` bash +scp azureadmin@$MOODLE_CONTROLLER_INSTANCE_IP:/moodle/db-backup.sql /tmp/moodle-db-backup.sql +``` + +To obtain a more recent SQL dump you run the commands appropriate for +your chosen database on the Controller VM. The following sections will +help with this task. + +#### Postgres + +Postgress provides a `pg_dump` command that can be used to take a +snapshot of the database via SSH. For example, use the following +command: + +``` bash +ssh azureadmin@$MOODLE_CONTROLLER_INSTANCE_IP 'pg_dump -Fc -h $MOODLE_DATABASE_DNS -U $MOODLE_DATABASE_ADMIN_USERNAME moodle > /moodle/db-snapshot.sql' +``` + +See the Postgres documentation for full details of the [`pg_dump`](https://www.postgresql.org/docs/9.5/static/backup-dump.html) command. + +#### MySQL + +MySQL provides a `mysql_dump` command that can be used to take a +snapshot of the database via SSH. For example, use the following +command: + +``` bash +ssh azureadmin@$MOODLE_CONTROLLER_INSTANCE_IP 'mysqldump -h $mysqlIP -u ${azuremoodledbuser} -p'${moodledbpass}' --databases ${moodledbname} | gzip > /moodle/db-backup.sql.gz' +``` + +## Backup and Recovery + +If you have set the `azureBackupSwitch` in the input parameters to `1` +then Azure will provide VM backups of your Gluster node. This is +recommended as it contains both your Moodle code and your sitedata. +Restoring a backed up VM is outside the scope of this doc, but Azure's +documentation on Recovery Services can be found here: +https://docs.microsoft.com/en-us/azure/backup/backup-azure-vms-first-look-arm + +## Resizing your Database + +Note: This process involves site downtime and should therefore only be +carried out during a planned maintenance window. + +At the time of writing Azure does not support resizing MySQL or +Postgres databases. You can, however, create a new database instance, +with a different size, and change your config to point to that. To get +a different size database you'll need to: + + 1. [Place your Moodle site into maintenance + mode](https://docs.moodle.org/34/en/Maintenance_mode). You can do + this either via the web interface or the command line on the + controller VM. + 2. Perform an SQL dump of your database. See above for more details. + 3. Create a new Azure database of the size you want inside your + existing resource group. + 4. Using the details in your /moodle/html/moodle/config.php create a + new user and database matching the details in config.php. Make + sure to grant all rights on the db to the user. + 5. On the controller instance, change the db setting in + /moodle/html/moodle/config.php to point to the new database. + 6. Take Moodle site out of maintenance mode. + 7. Once confirmed working, delete the previous database instance. + +How long this takes depends entirely on the size of your database and +the speed of your VM tier. It will always be a large enough window to +make a noticeable outage. + +## Changing the SSL cert + +The self-signed cert generated by the template is suitable for very +basic testing, but a public website will want a real cert. After +purchasing a trusted certificate, it can be copied to the following +files to be ready immediately: + + - /moodle/certs/nginx.key: Your certificate's private key + - /moodle/certs/nginx.crt: Your combined signed certificate and trust chain certificate(s). + +## Managing Azure DDoS protection + +By default, every plublic IP is protected by Azure DDoS protection Basic SKU. +You can find more information about Azure DDoS protection Basic SKU [here](https://docs.microsoft.com/en-us/azure/virtual-network/ddos-protection-overview). + +If you want more protection, you can activate Azure DDoS protection Standard SKU by setting +the ddosSwith to true. You can find how to work with Azure DDoS +protection plan [here](https://docs.microsoft.com/en-us/azure/virtual-network/manage-ddos-protection#work-with-ddos-protection-plans). + +If you want to disable the Azure DDoS protection, you can follow the instruction +[here](https://docs.microsoft.com/en-us/azure/virtual-network/manage-ddos-protection#disable-ddos-for-a-virtual-network). + +Be careful, disabling the Azure DDoS protection on your vnet will not stop the fee. +You have to delete the Azure DDoS protection plan if you want to stop the fee. + +If you have deployed your cluster without Azure DDoS protection plan, you still can activate the +Azure 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). + +## Next Steps + + 1. [Retrieve configuration details using CLI](./Get-Install-Data.md) diff --git a/docs/Parameters.md b/docs/Parameters.md new file mode 100644 index 00000000..67700f9e --- /dev/null +++ b/docs/Parameters.md @@ -0,0 +1,762 @@ +# Moodle on Azure Parameters + +Our goal with these templates is to make it as easy as possible to +deploy a Moodle on Azure cluster that can be customized to your +specific needs. To that end we provide a great manay configuration +options. This document attempts to document all these parameters, +however, like all documentation it can sometimes fall behind. For a +canonical reference you should review the `azuredeploy.json` file. + +## Extracting documentation from azuredeploy.json + +To make it a litte easier to read `azuredeploy.json` you might want to +run the following commands which will extract the necessary +information and display it in a more readable form. + +```bash +sudp apt install jq +``` + +``` bash +jq -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 +``` + +## Available Parameters + +### _artifactsLocation + +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. + +Type: string + +Possible Values: null + +Default: https://raw.githubusercontent.com/Azure/Moodle/master/ + + +### _artifactsLocationSasToken + +The sasToken required to access _artifactsLocation. When the template is deployed using the accompanying scripts, a sasToken will be automatically generated. + +Type: securestring + +Possible Values: null + +Default: + + +### applyScriptsSwitch + +Switch to process or bypass all scripts/extensions + +Type: bool + +Possible Values: null + +Default: true + + +### azureBackupSwitch + +Switch to configure AzureBackup and enlist VM's + +Type: bool + +Possible Values: null + +Default: false + + +### redisDeploySwitch + +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). + +Type: bool + +Possible Values: null + +Default: false + + +### vnetGwDeploySwitch + +Switch to deploy a virtual network gateway or not + +Type: bool + +Possible Values: null + +Default: false + + +### installObjectFsSwitch + +Switch to install Moodle Object FS plugins (with Azure Blob storage) + +Type: bool + +Possible Values: null + +Default: false + + +### installO365pluginsSwitch + +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. + +Type: bool + +Possible Values: null + +Default: false + + +### installGdprPluginsSwitch + +(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. + +Type: bool + +Possible Values: null + +Default: false + + +### htmlLocalCopySwitch + +Switch to create a local copy of /moodle/html or not + +Type: bool + +Possible Values: null + +Default: true + + +### ddosSwitch + +Switch to create a DDoS protection plan + +Type: bool + +Possible Values: null + +Default: false + + +### enableAccelNwForCtlrVmSwitch + +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. + +Type: bool + +Possible Values: null + +Default: false + + +### enableAccelNwForOtherVmsSwitch + +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. + +Type: bool + +Possible Values: null + +Default: true + + +### httpsTermination + +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. + +Type: string + +Possible Values: ["VMSS","AppGw","None"] + +Default: VMSS + + +### siteURL + +URL for Moodle site + +Type: string + +Possible Values: null + +Default: www.example.org + + +### moodleVersion + +The Moodle version you want to install. + +Type: string + +Possible 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"] + +Default: MOODLE_35_STABLE + + +### sshPublicKey + +ssh public key + +Type: string + +Possible Values: null + +Default: null + + +### sshUsername + +ssh user name + +Type: string + +Possible Values: null + +Default: azureadmin + + +### controllerVmSku + +VM size for the controller VM + +Type: string + +Possible Values: null + +Default: Standard_DS1_v2 + + +### webServerType + +Web server type + +Type: string + +Possible Values: ["apache","nginx"] + +Default: apache + + +### autoscaleVmSku + +VM size for autoscaled web VMs + +Type: string + +Possible Values: null + +Default: Standard_DS2_v2 + + +### autoscaleVmCountMax + +Maximum number of autoscaled web VMs + +Type: int + +Possible Values: null + +Default: 10 + + +### autoscaleVmCountMin + +Minimum (also initial) number of autoscaled web VMs + +Type: int + +Possible Values: null + +Default: 1 + + +### osDiskStorageType + +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. + +Type: string + +Possible Values: ["Premium_LRS","Standard_LRS"] + +Default: Premium_LRS + + +### dbServerType + +Database type + +Type: string + +Possible Values: ["postgres","mysql","mssql"] + +Default: mysql + + +### dbLogin + +Database admin username + +Type: string + +Possible Values: null + +Default: dbadmin + + +### mysqlPgresVcores + +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. + +Type: int + +Possible Values: [1,2,4,8,16,32] + +Default: 2 + + +### mysqlPgresStgSizeGB + +MySql/Postgresql storage size in GB. Minimum 5GB, increase by 1GB, up to 1TB (1024 GB) + +Type: int + +Possible Values: null + +Default: 125 + + +### mysqlPgresSkuTier + +MySql/Postgresql sku tier + +Type: string + +Possible Values: ["Basic","GeneralPurpose","MemoryOptimized"] + +Default: GeneralPurpose + + +### mysqlPgresSkuHwFamily + +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. + +Type: string + +Possible Values: ["Gen4","Gen5"] + +Default: Gen5 + + +### mysqlVersion + +Mysql version + +Type: string + +Possible Values: ["5.6","5.7"] + +Default: 5.7 + + +### postgresVersion + +Postgresql version + +Type: string + +Possible Values: ["9.5","9.6"] + +Default: 9.6 + + +### sslEnforcement + +MySql/Postgresql SSL connection + +Type: string + +Possible Values: ["Disabled","Enabled"] + +Default: Disabled + + +### mssqlDbServiceObjectiveName + +MS SQL database service object names + +Type: string + +Possible Values: ["S1","S2","S3","S4","S5","S6","S7","S9"] + +Default: S1 + + +### mssqlDbSize + +MS SQL database size + +Type: string + +Possible Values: ["100MB","250MB","500MB","1GB","2GB","5GB","10GB","20GB","30GB","40GB","50GB","100GB","250GB","300GB","400GB","500GB","750GB","1024GB"] + +Default: 250GB + + +### mssqlDbEdition + +MS SQL DB edition + +Type: string + +Possible Values: ["Basic","Standard"] + +Default: Standard + + +### mssqlVersion + +Mssql version + +Type: string + +Possible Values: ["12.0"] + +Default: 12.0 + + +### fileServerType + +File server type: GlusterFS, NFS, and NFS-HA (2-VM highly available NFS cluster) + +Type: string + +Possible Values: ["gluster","nfs","nfs-ha","nfs-byo"] + +Default: nfs + + +### nfsByoIpExportPath + +IP address and export path of the BYO-NFS share when fileServerType == nfs-byo. E.g., 172.16.1.8:/msazure + +Type: string + +Possible Values: null + +Default: + + +### fileServerDiskSize + +Size per disk for gluster nodes or nfs server + +Type: int + +Possible Values: null + +Default: 127 + + +### fileServerDiskCount + +Number of disks in raid0 per gluster node or nfs server + +Type: int + +Possible Values: null + +Default: 4 + + +### fileServerVmSku + +VM size for the gluster or NFS-HA nodes + +Type: string + +Possible Values: null + +Default: Standard_DS2_v2 + + +### keyVaultResourceId + +(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. + +Type: string + +Possible Values: null + +Default: + + +### sslCertKeyVaultURL + +(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. + +Type: string + +Possible Values: null + +Default: + + +### sslCertThumbprint + +(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. + +Type: string + +Possible Values: null + +Default: + + +### caCertKeyVaultURL + +(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. + +Type: string + +Possible Values: null + +Default: + + +### caCertThumbprint + +(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. + +Type: string + +Possible Values: null + +Default: + + +### appGwSslCertKeyVaultResourceId + +(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. + +Type: string + +Possible Values: null + +Default: + + +### appGwSslCertKeyVaultSecretName + +(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. + +Type: string + +Possible Values: null + +Default: + + +### appGwSkuName + +(App Gateway https termination only) Name of the Applicate Gateway SKU + +Type: string + +Possible Values: ["Standard_Small","Standard_Medium","Standard_Large","WAF_Medium","WAF_Large"] + +Default: Standard_Medium + + +### appGwSkuTier + +(App Gateway https termination only) Tier of the Applicate Gateway + +Type: string + +Possible Values: ["Standard","WAF"] + +Default: Standard + + +### appGwSkuCapacity + +(App Gateway https termination only) Capacity instance count) of the Applicate Gateway + +Type: int + +Possible Values: null + +Default: 2 + + +### storageAccountType + +Storage Account type. This storage account is only for the Moodle ObjectFS plugin and/or the (currently disabled) Azure Files file share option + +Type: string + +Possible Values: ["Standard_LRS","Standard_GRS","Standard_ZRS"] + +Default: Standard_LRS + + +### searchType + +options of moodle global search + +Type: string + +Possible Values: ["none","azure","elastic"] + +Default: none + + +### tikaService + +options of enabling tika service for file searching in moodle + +Type: string + +Possible Values: ["none","tika"] + +Default: none + + +### azureSearchSku + +the search service level you want to create. + +Type: string + +Possible Values: ["free","basic","standard","standard2","standard3"] + +Default: basic + + +### azureSearchReplicaCount + +Replicas distribute search workloads across the service. You need 2 or more to support high availability (applies to Basic and Standard only). + +Type: int + +Possible Values: null + +Default: 3 + + +### azureSearchPartitionCount + +Partitions allow for scaling of document count as well as faster indexing by sharding your index over multiple Azure Search units. + +Type: int + +Possible Values: [1,2,3,4,6,12] + +Default: 1 + + +### azureSearchHostingMode + +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. + +Type: string + +Possible Values: ["default","highDensity"] + +Default: default + + +### elasticVmSku + +VM size for the elastic search nodes + +Type: string + +Possible Values: null + +Default: Standard_DS2_v2 + + +### tikaVmSku + +VM size for the tika search nodes + +Type: string + +Possible Values: null + +Default: Standard_DS2_v2 + + +### customVnetId + +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. + +Type: string + +Possible Values: null + +Default: + + +### vNetAddressSpace + +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). + +Type: string + +Possible Values: null + +Default: 172.31.0.0 + + +### gatewayType + +Virtual network gateway type + +Type: string + +Possible Values: ["Vpn","ER"] + +Default: Vpn + + +### vpnType + +Virtual network gateway vpn type + +Type: string + +Possible Values: ["RouteBased","PolicyBased"] + +Default: RouteBased + + +### loadBalancerSku + +Loadbalancer SKU + +Type: string + +Possible Values: ["Basic","Standard"] + +Default: Basic + + +### location + +Azure Location for all resources. + +Type: string + +Possible Values: null + +Default: [resourceGroup().location] + + diff --git a/docs/Preparation.md b/docs/Preparation.md new file mode 100644 index 00000000..ce49aa46 --- /dev/null +++ b/docs/Preparation.md @@ -0,0 +1,73 @@ +# Environment Preparation + +This document describes how to ensure your environment is configured +for working with Moodle on Azure. + +## Prerequisites + +In order to configure our deployment and tools we'll set up some +[environment variables](./Environment-Variables.md) to ensure consistency. + +## Required software + +We'll use a number of tools when working with Moodle on Azure. Let's +ensure they are all installed: + +``` shell +sudo apt-get update +sudo apt-get install wget -y +sudo apt-get openssh-client -y +``` + +The [Azure CLI](https://docs.microsoft.com/en-us/cli/azure/install-azure-cli-apt?view=azure-cli-latest) is also important: + +```bash +AZ_REPO=$(lsb_release -cs) +echo "deb [arch=amd64] https://packages.microsoft.com/repos/azure-cli/ $AZ_REPO main" | sudo tee /etc/apt/sources.list.d/azure-cli.list +sudo apt-key adv --keyserver packages.microsoft.com --recv-keys 52E16F86FEE04B979B07E28DB02C46DF417A0893 +sudo apt-get install apt-transport-https +sudo apt-get update && sudo apt-get install azure-cli +``` + +## Ensure we have a valid SSH key pair + +We use SSH for secure communication with our hosts. The following line +will check there is a valid SSH key available and, if not, create one. + +``` +if [ ! -f "$MOODLE_SSH_KEY_FILENAME" ]; then ssh-keygen -t rsa -N "" -f $MOODLE_SSH_KEY_FILENAME; fi +``` +## Checkout the Moodle ARM Template + +The Moodle Azure Resource Manager template is hosted on GitHub. We'll +checkout the template into our workspace. + +``` +git clone git@github.com:Azure/Moodle.git $MOODLE_AZURE_WORKSPACE/arm_template +``` + +# Validation + +After completing these steps we should have, amonst other things, a +complete checkout of the Moodle templates from GitHub: + +``` bash +ls $MOODLE_AZURE_WORKSPACE/arm_template +``` + +Results: + +``` expected_similarity=0.4 +azuredeploy.json azuredeploy.parameters.json CONTRIBUTE.md docs env.json etc images LICENSE LICENSE-DOCS metadata.json nested +README.md +``` + +We should also have a number of applications installed, such as the Azure CLI: + +``` bash +if hash az 2>/dev/null; then echo "Azure CLI Installed"; else echo "Missing dependency: Azure CLI"; fi +``` + +``` +AzureCLI Installed +``` diff --git a/docs/SslCert.md b/docs/SslCert.md new file mode 100644 index 00000000..a0c290a9 --- /dev/null +++ b/docs/SslCert.md @@ -0,0 +1,72 @@ +# SSL Certificate Management + +A valid SSL (TLS) certificate should be used with your domain name for the Moodle +site to be deployed using the templates. By default, the templates will configure +the HTTPS server with a self-signed SSL server certificate/private key, which can +be manually changed with your own valid SSL server certificate/private key after +the deployment. + +If you'd like to configure the Moodle cluster (to be deployed) with your own domain +and your valid SSL server certificate/private key, then you can do so by utilizing +[Azure Key Vault](https://azure.microsoft.com/en-us/services/key-vault/) and +configuring the related template parameters as described below. This support is +based on [another similar work](https://github.com/Azure/azure-quickstart-templates/tree/master/201-vmss-ubuntu-web-ssl) +and adapted to our situation. + +## Initial deployment + +To configure the Moodle cluster (to be deployed) with your purchased SSL certificate, +currently the related files should be stored in an Azure Key Vault as secrets, so that +Azure Resource Manager can reference when it deploys VMs as specified in templates. + +You can create your own Azure Key Vault and store your purchased SSL certificate (called +'import' in Azure Key Vault terminology) by following related documentation like +[this](https://docs.microsoft.com/en-us/azure/key-vault/key-vault-manage-with-cli2). +However, the related files must be stored in a specific format, so we created a +shell script (`keyvault.sh`) that will perform all necessary steps for this purpose. +To use this script, you'll first need to upload your SSL certificate/private key files +(.pem) to your deployment environment you set up by following the [preparation document](Preparation.md) +(a Linux command line). The .pem files should be as follows: + +- `cert.pem`: The SSL certificate only in PEM format +- `key.pem`: The private key for the SSL certificate only in PEM format +- `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. + +Once you updloaded the files to your deployment environment, you can run the following command +to create an Azure Key Vault on your subscription and store your SSL certificate, private key, and optionally +the intermediate CA certificate: + +``` bash +$ bash $MOODLE_AZURE_WORKSPACE/arm_template/etc/keyvault.sh cert.pem key.pem chain.pem +``` + +Make sure to set `` the same as the Azure region you'll be using to deploy the Moodle template. +Assign desired names for ``, `` (you can use an existing resource group) and ``. +`` is not very important in our deployment. Then you'll get outputs as follows: + +``` +... +Specified SSL cert/key .pem files are now stored in your Azure Key Vault and ready to be used by the template. +Use the following values for the related template parameters: + +- keyVaultResourceId: /subscriptions/206c66fc-a48c-480d-ad06-0d429e82c586/resourceGroups/keyvault/providers/Microsoft.KeyVault/vaults/mdl-kv +- sslCertKeyVaultURL: https://mdl-kv.vault.azure.net/secrets/mymoodlesitecert/4c88452fe72b4d469253af48348f4944 +- sslCertThumbprint: 56478E4F9555662476E2763D909F50B3DD26FF84 +- caCertKeyVaultURL: https://mdl-kv.vault.azure.net/secrets/camymoodlesitecert/684efab1f2124e71a2c809457d10808b +- caCertThumbprint: E6A3B45B062D509B3382282D196EFE97D5956CCB +Done +``` + +This example outputs assumes `"keyvault"` is used for ``, `"mdl-kv"` for ``, +and `"mymoodlesitecert"` for ``. Note that `caCertKeyVaultURL` and `caCertThumbprint` will be empty +if you didn't specify `chain.pem`. Then you can copy these outputs to the template's corresponding parameters, +and Azure Resource Manager will install the certificate and the private key on the deployed VMs and the deployed +HTTPS server will use this certificate and private key. + +## Certificate rotation + +Another important benefit of using Azure Key Vault is to handle certificate expiration/rotation automatically. +Unfortunately, the current implementation doesn't support the auto-rotation. So when it becomes near your SSL +certificate's expiry, you'll need to manually update the deployed certificate and private key files +(it's in `/moodle/certs/nginx.{crt,key}` on the controller VM) and restart all the web frontend VM instances. +We'll improve our implementation to support auto-rotation in the future. \ No newline at end of file diff --git a/docs/Test.md b/docs/Test.md new file mode 100644 index 00000000..09925873 --- /dev/null +++ b/docs/Test.md @@ -0,0 +1,9 @@ +# Test a Moodle Instance + +## Prerequisites + +It is obviously necessary to have a [Moodle cluster up and running](./Deploy.md). + +## Next Steps + + * [Delete all Resources](./Delete.md) diff --git a/docs/env.json b/docs/env.json new file mode 100644 index 00000000..28cbab8b --- /dev/null +++ b/docs/env.json @@ -0,0 +1,5 @@ +{ + "MOODLE_RG_NAME": "rgmoodlearm13tagging", + "MOODLE_RG_LOCATION": "canadacentral", + "MOODLE_DEPLOYMENT_NAME": "MainDeployment" +} diff --git a/env.json b/env.json new file mode 100644 index 00000000..838d537a --- /dev/null +++ b/env.json @@ -0,0 +1,5 @@ +{ + "MOODLE_RG_NAME": "rgmoodlearm12", + "MOODLE_RG_LOCATION": "canadacentral", + "MOODLE_DEPLOYMENT_NAME": "MainDeployment" +} diff --git a/etc/changeBranchInURL.sh b/etc/changeBranchInURL.sh new file mode 100755 index 00000000..e8b4ecd8 --- /dev/null +++ b/etc/changeBranchInURL.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +from=$1 +to=$2 + +sed -i s/%2F${from}%2F/%2F${to}%2F/g README.md +sed -i s#/${from}/#/${to}/#g azuredeploy.json diff --git a/etc/checkBaseUrls.sh b/etc/checkBaseUrls.sh new file mode 100755 index 00000000..c71dd6c4 --- /dev/null +++ b/etc/checkBaseUrls.sh @@ -0,0 +1,58 @@ +# Ensure that the Base URL for templates, scripts and deploy to Azure buttons +# is correctly set for the current git branch. + +# Correct values for locations +CURRENT_BRANCH=$(git branch | sed -n -e 's/^\* \(.*\)/\1/p') +echo "Current git branch is '$CURRENT_BRANCH'" + +BASE_TEMPLATE_URL=https://raw.githubusercontent.com/Azure/Moodle/$CURRENT_BRANCH/nested/ +echo "Base template URL: $BASE_TEMPLATE_URL" + +SCRIPT_LOCATION=https://raw.githubusercontent.com/Azure/Moodle/$CURRENT_BRANCH/scripts/ +echo "Script location: $SCRIPT_LOCATION" + +DEPLOY_TO_AZURE_URL=https://portal.azure.com/#create/Microsoft.Template/uri/https%3A%2F%2Fraw.githubusercontent.com%2FAzure%2FMoodle%2F$CURRENT_BRANCH%2Fazuredeploy.json +echo "Deploy to Azure URL: $DEPLOY_TO_AZURE_URL" + +VISUALIZE_URL=http://armviz.io/#/?load=https%3A%2F%2Fraw.githubusercontent.com%2FAzure%2FMoodle%2F$CURRENT_BRANCH%2Fazuredeploy.json +echo "Visualize template URL: $VISUALIZE_URL" + +# Check values in README.md + +VALUE=$(sed -n -e 's/.*deploybutton.png)](\([^)]*\)).*/\1/p' README.md) +if [[ "$VALUE" = "$DEPLOY_TO_AZURE_URL" ]] +then + echo "Deploy to Azure URL is set correctly" +else + echo "!!!!! Deploy to Azure URL is not set correctly in README.md, it is currently:" + echo $VALUE +fi + +VALUE=$(sed -n -e 's/.*visualizebutton.png)](\([^)]*\)).*/\1/p' README.md) +if [[ "$VALUE" = "$VISUALIZE_URL" ]] +then + echo "Visualize URL is set correctly" +else + echo "!!!!! Visualize URL is not set correctly in README.md, it is currently:" + echo $VALUE +fi + +# Check values in azuredeploy.json + +VALUE=$(sed -n -e 's/.*\"baseTemplateUrl\": \"\([^\"]*\)\",/\1/p' azuredeploy.json) +if [[ "$VALUE" = "$BASE_TEMPLATE_URL" ]] +then + echo "baseTemplateURL is set correctly" +else + echo "!!!!! baseTemplateURL is not set correctly, it is currently:" + echo $VALUE +fi + +VALUE=$(sed -n -e 's/.*\"scriptLocation\": \"\([^\"]*\)\",/\1/p' azuredeploy.json) +if [[ "$VALUE" = "$SCRIPT_LOCATION" ]] +then + echo "scriptLocation is set correctly" +else + echo "!!!!! scriptLocation is not set correctly, it is currently:" + echo $VALUE +fi diff --git a/etc/keyvault.sh b/etc/keyvault.sh new file mode 100755 index 00000000..82eba944 --- /dev/null +++ b/etc/keyvault.sh @@ -0,0 +1,163 @@ +#!/bin/bash + +# Based on https://github.com/Azure/azure-quickstart-templates/blob/master/201-vmss-ubuntu-web-ssl/keyvault.sh + +#set -e + +usage() +{ + echo usage: keyvault.sh ' ' + echo The cacertpem file is optional. The template will accept a self-signed cert and key. +} + +creategroup() +{ + + local group=$(az group show -g $rgname) + if [ -n "$group" ]; then + echo Resource Group $rgname already exists. Skipping creation. + else + # Create a resource group for the keyvault + az group create -n $rgname -l $location + fi +} + +createkeyvault() +{ + + az keyvault show -n $vaultname 2> /dev/null + if [ $? -eq 0 ] + then + echo Key Vault $vaultname already exists. Skipping creation. + else + echo Creating Key Vault $vaultname. + + creategroup + # Create the key vault + az keyvault create --name $vaultname --resource-group $rgname --location $location --enabled-for-template-deployment true --enabled-for-deployment true + fi +} + +convertcert() +{ + local cert=$1 + local key=$2 + local pfxfile=$3 + local pass=$4 + + echo Creating PFX $pfxfile + openssl pkcs12 -export -out $pfxfile -inkey $key -in $cert -password pass:$pass 2> /dev/null + if [ $? -eq 1 ] + then + echo problem converting $key and $cert to pfx + exit 1 + fi + + fingerprint=$(openssl x509 -in $cert -noout -fingerprint | cut -d= -f2 | sed 's/://g' ) +} + +convertcacert() +{ + local cert=$1 + local pfxfile=$2 + local pass=$3 + + echo Creating PFX $pfxfile + openssl pkcs12 -export -out $pfxfile -nokeys -in $cert -password pass:$pass 2> /dev/null + if [ $? -eq 1 ] + then + echo problem converting $cert to pfx + exit 1 + fi + + fingerprint=$(openssl x509 -in $cert -noout -fingerprint | cut -d= -f2 | sed 's/://g' ) +} + +storesecret() +{ + local secretfile=$1 + local name=$2 + filecontentencoded=$( cat $secretfile | base64 $base64_unwrap ) + +json=$(cat << EOF +{ +"data": "${filecontentencoded}", +"dataType" :"pfx", +"password": "${pwd}" +} +EOF +) + + jsonEncoded=$( echo $json | base64 $base64_unwrap ) + + r=$(az keyvault secret set --vault-name $vaultname --name $name --value $jsonEncoded) + if [ $? -eq 1 ] + then + echo problem storing secret $name in $vaultname + exit 1 + fi + + id=$(az keyvault secret show --vault-name $vaultname --name $name --query id -o tsv) + echo Secret ID is $id +} + +# We need at least 6 parameters +if [ "$#" -lt 6 ]; then + usage + exit +fi + +# The base64 command on OSX does not know about the -w parameter, but outputs unwrapped base64 by default +base64_unwrap="-w 0" +[[ $(uname) == "Darwin" ]] && base64_unwrap="" + +vaultname=$1 +rgname=$2 +location=$3 +secretname=$4 +certfile=$5 +keyfile=$6 +cacertfile=$7 + +# Create a random password with 33 bytes of entropy +# I picked 33 so the last character will not be = +pwd=$(dd if=/dev/urandom bs=32 count=1 2>/dev/null | base64) + +certpfxfile=${certfile%.*crt}.pfx +cacertpfxfile=${cacertfile%.*crt}.pfx +casecretname=ca$secretname + +createkeyvault + +# converting SSL cert to pfx +convertcert $certfile $keyfile $certpfxfile $pwd +certprint=$fingerprint +echo $certpfxfile fingerprint is $fingerprint +# storing pfx in keyvault +echo Storing $certpfxfile as $secretname +storesecret $certpfxfile $secretname +certid=$id +rm -f $certpfxfile + +if [ ! -z $cacertfile ] +then + # converting CA cert to pfx + convertcacert $cacertfile $cacertpfxfile $pwd + echo $cacertpfxfile fingerprint is $fingerprint + cacertprint=$fingerprint + # storing pfx in key vault + echo Storing $cacertpfxfile as $casecretname + storesecret $cacertpfxfile $casecretname + cacertid=$id + rm -f $cacertpfxfile +fi + +echo "Specified SSL cert/key .pem files are now stored in your Azure Key Vault and ready to be used by the template." +echo "Use the following values for the related template parameters:" +echo +echo "- keyVaultResourceId: $(az keyvault show --name $vaultname --query id -o tsv)" +echo "- sslCertKeyVaultURL: $certid" +echo "- sslCertThumbprint: $certprint" +echo "- caCertKeyVaultURL: $cacertid" +echo "- caCertThumbprint: $cacertprint" +echo Done diff --git a/etc/travis.py b/etc/travis.py new file mode 100755 index 00000000..fc6d754d --- /dev/null +++ b/etc/travis.py @@ -0,0 +1,5 @@ +#!/usr/bin/env python3 + +from travis.DeploymentTester import DeploymentTester + +DeploymentTester().run() diff --git a/etc/travis/Configuration.py b/etc/travis/Configuration.py new file mode 100644 index 00000000..deed8817 --- /dev/null +++ b/etc/travis/Configuration.py @@ -0,0 +1,85 @@ +import json +import os +import time + +from azure.mgmt.resource.resources.v2017_05_10.models import DeploymentMode + + +class Configuration: + def __init__(self): + self.deployment_name = 'azuredeploy' + self.client_id = os.getenv('SPNAME') + self.secret = os.getenv('SPPASSWORD') + self.tenant_id = os.getenv('SPTENANT') + self.location = os.getenv('LOCATION', 'westus2') + self.source_branch = self.identify_source_branch() + self.fullci_branches = os.getenv('FULLCI_BRANCHES', 'master').split(':') + self.commit_message = os.getenv('TRAVIS_COMMIT_MESSAGE', None) + self.ssh_key = self.identify_ssh_key() + self.resource_group = self.identify_resource_group() + self.deployment_properties = self.generate_deployment_properties() + + def identify_resource_group(self): + resource_group = os.getenv('RESOURCEGROUP') + if resource_group is None: + resource_group = 'azmdl-travis-' + os.getenv('TRAVIS_BUILD_NUMBER', 'manual-{}'.format(time.time())) + return resource_group + + def identify_ssh_key(self): + ssh_key = os.getenv('SPSSHKEY') + if ssh_key is None: + with open('azure_moodle_id_rsa.pub', 'r') as sshkey_fd: + ssh_key = sshkey_fd.read() + return ssh_key + + def generate_deployment_properties(self): + with open('azuredeploy.json', 'r') as template_fd: + template = json.load(template_fd) + + with open('azuredeploy.parameters.json', 'r') as parameters_fd: + parameters = json.load(parameters_fd) + parameters = parameters['parameters'] + parameters['sshPublicKey']['value'] = self.ssh_key + parameters['_artifactsLocation'] = {'value': self.identify_artifacts_location()} + + return { + 'mode': DeploymentMode.incremental, + 'template': template, + 'parameters': parameters, + } + + def identify_artifacts_location(self): + slug = os.getenv('TRAVIS_PULL_REQUEST_SLUG') + if not slug: + slug = os.getenv('TRAVIS_REPO_SLUG') + return "https://raw.githubusercontent.com/{}/{}/".format(slug, self.source_branch) + + def identify_source_branch(self): + branch = os.getenv('TRAVIS_PULL_REQUEST_BRANCH') + if not branch: + branch = os.getenv('TRAVIS_BRANCH') + return branch + + def is_valid(self): + valid = True + + for key, value in vars(self).items(): + if value is None: + valid = False + print('(missing configuration for {})'.format(key)) + + if self.deployment_properties['parameters']['_artifactsLocation']['value'] is None: + valid = False + print('(could not identify _artifactsLocation)') + + return valid + + def should_run_full_ci(self): + if self.source_branch in self.fullci_branches: + return True + + message = self.commit_message.upper() + if '[FULL CI]' in message or '[FULLCI]' in message: + return True + + return False diff --git a/etc/travis/DeploymentTester.py b/etc/travis/DeploymentTester.py new file mode 100644 index 00000000..2ff670f4 --- /dev/null +++ b/etc/travis/DeploymentTester.py @@ -0,0 +1,177 @@ +import os +import pycurl +import sys +import tempfile +import time +import urllib +from io import BytesIO +from pycurl import Curl + +from azure.mgmt.resource import ResourceManagementClient +from azure.mgmt.subscription import SubscriptionClient +from msrestazure.azure_active_directory import ServicePrincipalCredentials + +from travis.Configuration import Configuration + + +class DeploymentTester: + @staticmethod + def elapsed(since): + elapsed = int(time.time() - since) + elapsed = '{:02d}:{:02d}:{:02d}'.format(elapsed // 3600, (elapsed % 3600 // 60), elapsed % 60) + return elapsed + + def __init__(self): + self.config = Configuration() + self.deployment = None + + self.credentials = None + """:type : ServicePrincipalCredentials""" + + self.resource_client = None + """:type : ResourceManagementClient""" + + def run(self): + should_delete_resource_group = False + try: + self.check_configuration() + self.login() + self.create_resource_group() + self.validate() + if not self.config.should_run_full_ci(): + print('\n\nBasic CI tests successful.') + should_delete_resource_group = True + return + self.deploy() + self.moodle_smoke_test() + self.moodle_admin_login() + print('\n\nFull CI tests successful!') + should_delete_resource_group = True + finally: + if should_delete_resource_group: + self.delete_resource_group() + + def check_configuration(self): + print('\nChecking configuration...') + if not self.config.is_valid(): + print('No Azure deployment info given, skipping test deployment and exiting.') + print('Further information: https://github.com/Azure/Moodle#automated-testing-travis-ci') + sys.exit() + artifacts_location = self.config.deployment_properties['parameters']['_artifactsLocation'] + print('- Detected "_artifactsLocation": ' + artifacts_location['value']) + print("(all check)") + + def login(self): + print('\nLogging in...') + self.credentials = ServicePrincipalCredentials( + client_id=self.config.client_id, + secret=self.config.secret, + tenant=self.config.tenant_id, + ) + print('(got credentials)') + subscription_client = SubscriptionClient(self.credentials) + subscription = next(subscription_client.subscriptions.list()) + print('(found subscription)') + self.resource_client = ResourceManagementClient(self.credentials, subscription.subscription_id) + print("(logged in)") + + def create_resource_group(self): + print('\nCreating group "{}" on "{}"...'.format(self.config.resource_group, self.config.location)) + self.resource_client.resource_groups.create_or_update(self.config.resource_group, + {'location': self.config.location}) + print('(created)') + + def validate(self): + print('\nValidating template...') + + validation = self.resource_client.deployments.validate(self.config.resource_group, + self.config.deployment_name, + self.config.deployment_properties) + if validation.error is not None: + print("*** VALIDATION FAILED ({}) ***".format(validation.error)) + print(validation.error.message) + for detail in validation.error.details: + print("- {}:\n{}".format(detail.code, detail.message)) + sys.exit(1) + + print("(valid)") + + def deploy(self): + print('\nDeploying template, feel free to take a nap...') + deployment = self.resource_client.deployments.create_or_update(self.config.resource_group, + self.config.deployment_name, + self.config.deployment_properties) + """:type : msrestazure.azure_operation.AzureOperationPoller""" + started = time.time() + while not deployment.done(): + print('... after {} still "{}" ...'.format(self.elapsed(started), deployment.status())) + deployment.wait(60) + print("WAKE UP! After {} we finally got status {}.".format(self.elapsed(started), deployment.status())) + + print("Checking deployment response...") + properties = deployment.result(0).properties + if properties.provisioning_state != 'Succeeded': + print("*** DEPLOY FAILED ***") + print('Provisioning state: ' + properties.provisioning_state) + sys.exit(1) + self.load_deployment_outputs(properties.outputs) + print("(success)") + + def load_deployment_outputs(self, outputs): + self.deployment = {} + for key, value in outputs.items(): + self.deployment[key] = value['value'] + print("- Found: " + key) + + def moodle_smoke_test(self): + print("\nMoodle Smoke Test...") + url = 'https://' + self.deployment['siteURL'] + curl = Curl() + curl.setopt(pycurl.URL, url) + curl.setopt(pycurl.SSL_VERIFYPEER, False) + curl.setopt(pycurl.WRITEFUNCTION, lambda x: None) + curl.perform() + status = curl.getinfo(pycurl.HTTP_CODE) + if status != 200: + print("*** DEPLOY FAILED ***") + print('HTTP Status Code: {}'.format(status)) + sys.exit(1) + print('(ok: {})'.format(status)) + + def moodle_admin_login(self): + print("\nLogging in into Moodle as 'admin'...") + response = self.moodle_admin_login_curl() + if 'Admin User' not in response: + print("*** FAILED: 'Admin User' keyword not found ***") + sys.exit(1) + print('(it worked)') + + def moodle_admin_login_curl(self): + fd, path = tempfile.mkstemp() + try: + response = BytesIO() + url = 'https://' + self.deployment['siteURL'] + '/login/index.php' + curl = Curl() + curl.setopt(pycurl.URL, url) + curl.setopt(pycurl.SSL_VERIFYPEER, False) + curl.setopt(pycurl.WRITEFUNCTION, response.write) + curl.setopt(pycurl.POST, True) + curl.setopt(pycurl.COOKIEJAR, path) + curl.setopt(pycurl.COOKIEFILE, path) + post = urllib.parse.urlencode({'username': 'admin', 'password': self.deployment['moodleAdminPassword']}) + curl.setopt(pycurl.POSTFIELDS, post) + curl.setopt(pycurl.FOLLOWLOCATION, True) + curl.perform() + status = curl.getinfo(pycurl.HTTP_CODE) + if status != 200: + print("*** FAILED: {} ***".format(status)) + sys.exit(1) + response = response.getvalue().decode('utf-8') + finally: + os.remove(path) + return response + + def delete_resource_group(self): + print('\n\nDeleting the resource group for this passing build...') + self.resource_client.resource_groups.delete(self.config.resource_group, polling=False) + print('(delete initiated, not polling)') diff --git a/etc/travis/__init__.py b/etc/travis/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/etc/updateDocsParametersMd.sh b/etc/updateDocsParametersMd.sh new file mode 100755 index 00000000..622d1dfe --- /dev/null +++ b/etc/updateDocsParametersMd.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +# Should be run at the git repo root as: $ ./etc/updateDocsParameters.sh + +dpkg -l jq &> /dev/null || sudo apt install jq + +sed -i '/## Available Parameters/q' docs/Parameters.md +echo >> docs/Parameters.md +jq -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 \ No newline at end of file diff --git a/images/stack_diagram.jpg b/images/stack_diagram.jpg deleted file mode 100644 index bda6c3f7..00000000 Binary files a/images/stack_diagram.jpg and /dev/null differ diff --git a/images/stack_diagram.png b/images/stack_diagram.png new file mode 100644 index 00000000..7e07c494 Binary files /dev/null and b/images/stack_diagram.png differ diff --git a/loadtest/Azure_Login.md b/loadtest/Azure_Login.md new file mode 100644 index 00000000..37c22d6d --- /dev/null +++ b/loadtest/Azure_Login.md @@ -0,0 +1,41 @@ +# Login to Azure + +Before we start a load test session we need to first veriy that we are +logged in to Azure using the CLI. + +## Azure Login + +``` bash +az login --username $AZURE_USERNAME --password $AZURE_PASWORD +``` + +Note that if your username or password has any special characters in +it, such as '$' this may fail. You can login using a browser using `az login`. + +``` bash +az account set --subscription $AZURE_SUBSCRIPTION_ID +``` + +## Validation + +``` bash +az account show +``` + +Results: + +``` +{ + "environmentName": "AzureCloud", + "id": "325e7c34-99fb-4190-aa87-1df746c67705", + "isDefault": true, + "name": "Ross Dev Account", + "state": "Enabled", + "tenantId": "72f988bf-86f1-41af-91ab-2d7cd011db47", + "user": { + "name": "rogardle@microsoft.com", + "type": "user" + } +} +``` + diff --git a/loadtest/Deploy_Load_Test_VM.md b/loadtest/Deploy_Load_Test_VM.md new file mode 100644 index 00000000..92dc7c31 --- /dev/null +++ b/loadtest/Deploy_Load_Test_VM.md @@ -0,0 +1,125 @@ +# Setting up a test host + +To run load tests using the resources in this directory, you'll want +to spin up an Ubuntu VM (let's call it the jMeter host) in your Azure +subscription. This should be located in the same region as your Moodle +cluster in order to avoid egress charges. Once your VM is ready, you +need to install Java and [jMeter](https://jmeter.apache.org/). + +## Prerequisites + +To make things consistent across different sessions load testing Moodle we +should [configure the moodle environment](../docs/Preparation.md). + +We will want to use a consistent resource group name in order to avoid +wasting resource in these tests: + +``` +MOODLE_RG_NAME=loadtest +``` + +And we'll need a name for our load test VM: + +``` +MOODLE_LOAD_TEST_VM_NAME=LoadTestVM +``` + +## Deploy the Load Test VM + +First you need a resource group within which all your resources will be deployed. + +``` bash +az group create --name $MOODLE_RG_NAME --location $MOODLE_RG_LOCATION +``` + +Now we can create our VM in this group. The following command will +create the VM and, if necessary, generate the SSH keys. + +``` bash +az vm create --resource-group $MOODLE_RG_NAME --name $MOODLE_LOAD_TEST_VM_NAME --image UbuntuLTS --generate-ssh-keys +``` + +Results: + +``` json +{ + "fqdns": "", + "id": "/subscriptions/325e7c34-99fb-4190-aa87-1df746c67705/resourceGroups/loadtestvm/providers/Microsoft.Compute/virtualMachines/LoadTestVM", + "location": "southcentralus", + "macAddress": "00-0D-3A-70-91-57", + "powerState": "VM running", + "privateIpAddress": "10.0.0.4", + "publicIpAddress": "13.84.131.173", + "resourceGroup": "loadtestvm", + "zones": "" +} +``` + +You will need the IP number from this output. For convenience we'll +place it into an environment variable: + +``` bash +ipAddress=$(az network public-ip show --name ${MOODLE_LOAD_TEST_VM_NAME}PublicIP --resource-group $MOODLE_RG_NAME --query "ipAddress" --output tsv) +echo $ipAddress +``` + +We 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`): + +``` bash +ssh -o StrictHostKeyChecking=no $ipAddress "rm -Rf Moodle; git clone git://github.com/Azure/Moodle.git" +``` + +Now we can install the load testing scripts, we will have these loaded +via the `.profile` so that they are always availble. + +``` bash +ssh $ipAddress 'echo ". ~/Moodle/loadtest/loadtest.sh" >> ~/.profile' +``` + +This script provides some helper functions for installing dependencies +on the VM. + +``` bash +ssh $ipAddress 'install_java_and_jmeter; install_az_cli' +``` + +We need to login to Azure using the CLI. The command below is +convenient but is not secure since it stores your password in clear +text in an environment variable. However, it is convenient for test +purposes. + +``` bash +ssh $ipAddress "az login --username $AZURE_LOGIN --password $AZURE_PASSWORD; az account set --subscription $AZURE_SUBSCRIPTION_ID" +``` + +## Validation + +Finally, we will verify that key dependencies have been installed. First lets check Java is present: + +``` bash +ssh -o StrictHostKeyChecking=no $ipAddress "java -version" +``` + +Results: + +``` +openjdk version "1.8.0_151" +OpenJDK Runtime Environment (build 1.8.0_151-8u151-b12-0ubuntu0.16.04.2-b12) +OpenJDK 64-Bit Server VM (build 25.151-b12, mixed mode) +``` + +We will also need to confirm the Azure CLI is present: + +``` bash +ssh -o StrictHostKeyChecking=no $ipAddress "if hash az 2>/dev/null; then echo "Azure CLI Installed"; else echo "Missing dependency: Azure CLI"; fi" +``` + +Results: + +``` +Azure CLI Installed +``` + + + + diff --git a/loadtest/README.md b/loadtest/README.md new file mode 100644 index 00000000..a3ca9a4f --- /dev/null +++ b/loadtest/README.md @@ -0,0 +1,139 @@ +# Load-Testing Deployed Moodle Cluster + +This directory currently contains utility scripts, a Moodle test +course, and an Apache jMeter test plan that can be used to load-test a +Moodle cluster on Azure using the Azure Resource Manager templates in +this repository. + +## Prerequisites + +To run load tests using the resources in this directory, you'll want +to spin up a VM to manage the [Load Test](Deploy_Load_Test_VM.md) in +your Azure subscription. This VM will generate the traffic, running it +in Azure will minimize network charges. + +## Deploying Moodle using templates and running load test + +Once dependencies are installed, you can initiate the load testing +process by using included utility scripts. These scripts will: + +* deploy a Moodle cluster +* set up the test course in Moodle +* enrol students for the course +* run a synthetic test workload using jMeter +* teardown the test cluster + +Use the function `deploy_run_test1_teardown` to perform all these +steps. This function takes 18 parameters in the following order: + +See the included example `run_load_test_example` in +`loadtest/loadtest.sh`. At the time of writing this example is +configured as follows: + +``` bash +ssh $ipAddress "az login --username $AZURE_LOGIN --password $AZURE_PASSWORD; az account set --subscription $AZURE_SUBSCRIPTION_ID; run_load_test_example" +``` + +Running this example will deploy a cluster with the following configuration: + +* Apache web server +* Standard_DS2_v2 Azure VM SKU +* mysql database (with 200 DTU and 125GB DB size) +* NFS file share (with 2 disks and 128GB disk size each) +* uses your SSH pub key in `~/.ssh/id_rsa` + +[NOTE ON SSH KEYS] Ensure your `~/.ssh/id_rsa` has been added to ssh-agent using `eval $(ssh-agent)` and `ssh-add`). + +Once the Moodle cluster is deployed and configured with course and +student data (using [moosh](https://moosh-online.com/) it will run the +synthetic workload with designated number of concurrent threads (in +the example we use 1600 thread) for the designated duration and rampup +time (18000 seconds = 5 hours duration, 4800 seconds rampup time in +the example). + +## Test plans + +We'd like to offer test plans that are as realistic as possible, so that potential +Moodle users on Azure can have better confidence with their Moodle deployment on Azure. +We are just starting out in that direction and here are descriptions of currently +available test plans. + +### Simple Scenario [simple-test-1.jmx](./simple-test-1.jmx) + +This test plan is a simple scenario that performs the following operations repeatedly: + +* Login to the Moodle site +* View the test course +* View any resource if any +* View a forum in the test course +* View any forum discussion +* Post a new discussion topic +* Take a quiz and submit + +The scripts in [loadtest.sh](./loadtest.sh) are tailored for this test plan. + +### Moodle Data Stress Testing [simple-test-2.jmx](./simple-test-2.jmx) + +Currently [loadtest.sh](./loadtest.sh) doesn't have any tailored scripts for this +test plan. Therefore, this test plan will need to be executed by issuing the +actual jmeter command with properly modified parameters manually, or it'd be +greatly appreciated if someone can contribute better support for this test plan +in [loadtest.hs](./loadtest.sh). + +The purpose of this test plan is to try stressing the moodledata directory +in a shared file system (either a gluster volume or an NFS share, depending +on the choice). Initially attaching a random file in a forum discussion post +was tried, but for some reason (probably due to my lack of understanding +in PHP/web interaction), files were not attached. I instead tried to upload +random files to each test Moodle users's Private Files area, and it did work. +This test plan basically performs the following operations repeatedly: + +* Login to the Moodle site +* Open the Moodle user's Private Files repository +* Upload a randomly generated file (of a random size within a hard-coded range) +* Save the change + +This way, we were able to populate the shared moodledata directory with +random files in Moodle users' Private Files repositories. The mechanism +to generate random files is not so efficient, so that's currently what +slows down the upload speed, and any improvement in that BeanShell preprocessor +code would be great. Note that the uploaded files have to be different. +Moodle seems so good at deduplicating that a single file uploaded multiple +times by different users won't increase the file system usage beyond its +single copy. + +It'd be also great if we add a download operation step in the test plan, +and it's left as a future work item. + +### Latency-Sensitive Stress Testing [time-gated-exam-test.jmx](./time-gated-exam-test.jmx) + +This test stresses the deployed Moodle cluster with 1000 emulated students +trying to get in an exam (quiz) that's initially closed and will be opened +at the designated exam start time (have to be manually set on the test course's +corresponding quiz's Settings). Once the exam start time passes, each emulated +student continues taking the exam for 10 times. + +This test has been used to find out how responsive +a deployed Moodle cluster can be on very latency-sensitive workloads. We've been +using this test with different file server types to find out which file server +type offers best response times. + +## Please contribute + +It'd be great if we have more test plans, and make other parameters configurable (for +example, make the auto-scaling thresholds configurable, which actually requires +some changes in the templates as well). The currently available test plans +also have hard-coded database type (JDBC connection string) that won't work +for Postgres SQL server, so making it work would be also greatly appreciated. +Also, if you run this load test with any parameters, it'd be great to share +the numeric results so that we can have more performance data on various +configurations. Here is [a link to an Excel spreadsheet](https://1drv.ms/x/s!Aj6KpM6lFGAjgd4D6IV8_6M42q9omA) +where anyone can share their load testing results. + +## Acknowledgement + +The original test course and the test plan were generously provided by +[Catalyst](https://github.com/catalyst) as part of this template modernization +project. jMeter is a great load testing tool, and also thanks to [moosh](http://moosh-online.com/), +the whole process could be automated without too much difficulty, which was +really nice. diff --git a/loadtest/azuredeploy.parameters.loadtest.defaults.json b/loadtest/azuredeploy.parameters.loadtest.defaults.json new file mode 100644 index 00000000..525f2eb0 --- /dev/null +++ b/loadtest/azuredeploy.parameters.loadtest.defaults.json @@ -0,0 +1,16 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "autoscaleVmSku": { "value": "__WEB_VM_SKU__" }, + "dbServerType": { "value": "__DB_SERVER_TYPE__" }, + "mysqlPgresVcores": { "value": 2 }, + "mysqlPgresStgSizeGB": { "value": 125 }, + "webServerType": { "value": "__WEB_SERVER_TYPE__" }, + "fileServerType": { "value": "__FILE_SERVER_TYPE__" }, + "fileServerDiskCount": { "value": 2 }, + "fileServerDiskSize": { "value": 128 }, + "redisDeploySwitch": { "value": false }, + "sshPublicKey": { "value": "__SSH_PUB_KEY__" } + } +} diff --git a/loadtest/loadtest.sh b/loadtest/loadtest.sh new file mode 100644 index 00000000..a5b6c529 --- /dev/null +++ b/loadtest/loadtest.sh @@ -0,0 +1,327 @@ +#!/bin/bash + +# This is not fully tested. Just documenting what's needed. +function install_java_and_jmeter +{ + sudo apt update || return 1 + sudo apt install -y openjdk-8-jdk || return 1 + + wget -O apache-jmeter-4.0.tgz http://www-us.apache.org/dist/jmeter/binaries/apache-jmeter-4.0.tgz || return 1 + tar xfz apache-jmeter-4.0.tgz -C ~ + mkdir -p ~/bin + ln -s ~/apache-jmeter-4.0/bin/jmeter ~/bin/jmeter + rm apache-jmeter-4.0.tgz + + 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 + tar xfz mysql-connector-java-5.1.45.tar.gz + mv mysql-connector-java-5.1.45/mysql-connector-java-5.1.45-bin.jar ~/apache-jmeter-4.0/lib + rm -rf mysql-connector-java-5.1.45* + + wget -O postgres-42.2.1.jar https://jdbc.postgresql.org/download/postgresql-42.2.1.jar || return 1 + mv postgres-42.2.1.jar ~/apache-jmeter-4.0/lib + + # Have to have jmeter plugins manager and have it download the needed plugins in advance... + 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 + mv jmeter-plugins-manager-0.19.jar ~/apache-jmeter-4.0/lib/ext + + wget -O cmdrunner-2.0.jar http://search.maven.org/remotecontent?filepath=kg/apc/cmdrunner/2.0/cmdrunner-2.0.jar || return 1 + mv cmdrunner-2.0.jar ~/apache-jmeter-4.0/lib + java -cp ~/apache-jmeter-4.0/lib/ext/jmeter-plugins-manager-0.19.jar org.jmeterplugins.repository.PluginManagerCMDInstaller + # TODO Hard-coded .jmx file here. Do this for each individual .jmx file + wget -O tmp-for-plugin-install.jmx https://raw.githubusercontent.com/Azure/Moodle/master/loadtest/simple-test-1.jmx || return 1 + ~/apache-jmeter-4.0/bin/PluginsManagerCMD.sh install-for-jmx tmp-for-plugin-install.jmx + rm tmp-for-plugin-install.jmx +} + +function install_az_cli +{ + local az_repo=$(lsb_release -cs) + echo "deb [arch=amd64] https://packages.microsoft.com/repos/azure-cli/ $az_repo main" | sudo tee /etc/apt/sources.list.d/azure-cli.list + sudo apt-key adv --keyserver packages.microsoft.com --recv-keys 52E16F86FEE04B979B07E28DB02C46DF417A0893 + sudo apt-get install -y apt-transport-https || return 1 + sudo apt-get update && sudo apt-get install -y azure-cli || return 1 +} + +function check_if_logged_on_azure +{ + az account show --query id -o tsv > /dev/null 2>&1 + if [ $? != "0" ]; then + echo "Not logged on to Azure. Run 'az login' first and make sure subscription is set to your desired one." + return 1 + fi +} + +function show_command_to_run +{ + echo "Running command: $*" +} + +function check_db_sku_params +{ + local vcores=${1} + local size=${2} # In GB + + if [ "$vcores" != 1 -a "$vcores" != 2 -a "$vcores" != 4 -a "$vcores" != 8 -a "$vcores" != 16 -a "$vcores" != 32 ]; then + echo "Invalid vCores ($vcores). Only allowed are 1, 2, 4, 8, 16, 32." + return 1 + fi + if [ -z "${size##*[!0-9]*}" ] || [ "$size" -lt 5 ] || [ "$size" -gt 1024 ]; then + echo "Invalid DB size ($size). Only allowed are 5, 6, 7, ..., 1024." + return 1 + fi + # TODO Add other SKU params: Tiers (Basic/GeneralPurpose/MemoryOptimized), HW family (Gen4/Gen5) +} + +# TODO hard-coded Azure location in global variable. Parametrize this later. +MOODLE_RG_LOCATION=southcentralus + +function deploy_moodle_with_some_parameters +{ + check_if_logged_on_azure || return 1 + + local resource_group=${1} # Azure resource group where templates will be deployed + local template_url=${2} # Github URL of the top template to deploy + local parameters_template_file=${3} # Local parameter template file + local web_server_type=${4} # E.g., apache or nginx + local web_vm_sku=${5} # E.g., Standard_DS2_v2 + local db_server_type=${6} # E.g., mysql or postgres + local db_vcores=${7} # 1, 2, 4, 8, 16, 32 only + local db_size_gb=${8} # 5 to 1024, integer only + local file_server_type=${9} # E.g., nfs or gluster + local file_server_disk_count=${10} # 2, 3, 4 + local file_server_disk_size=${11} # in GB + local redis_cache=${12} # Redis cache choice. Currently 'true' or 'false' only. + local ssh_pub_key=${13} # Your ssh authorized_keys content + local no_wait_flag=${14} # Must be "--no-wait" to be passed to az + + check_db_sku_params $db_vcores $db_size_gb || return 1 + + local cmd="az group create --resource-group $resource_group --location $MOODLE_RG_LOCATION" + show_command_to_run $cmd + eval $cmd || return 1 + + local deployment_name="${resource_group}-deployment" + local cmd="az group deployment 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'" + show_command_to_run $cmd + eval $cmd +} + +function delete_resource_group +{ + check_if_logged_on_azure || return 1 + + local resource_group=${1} + local cmd="az group delete --resource-group $resource_group" + show_command_to_run $cmd + eval $cmd +} + +function install_moosh +{ + # 'composer install' keeps failing, so try apt... + sudo apt-add-repository "deb http://ppa.launchpad.net/zabuch/ppa/ubuntu $(lsb_release -sc) main" + sudo apt-get update || true + sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys CA1F0167ECFEA95 + sudo apt-get install --assume-yes moosh + + # sudo apt update || return 1 + # sudo apt install -y composer || return 1 + # cd ~ + # git clone git://github.com/tmuras/moosh.git || return 1 + # cd moosh + # composer install || sleep 30 && composer install || sleep 30 && composer install || return 1 + # mkdir -p ~/bin + # ln -s $PWD/moosh.php ~/bin/moosh +} + +MOODLE_PATH=/moodle/html/moodle + +function delete_course +{ + local course_id=${1} + + sudo -u www-data moosh --moodle-path=$MOODLE_PATH course-delete $course_id +} + +function create_course +{ + local course_id=${1} + + sudo -u www-data moosh --moodle-path=$MOODLE_PATH course-create --idnumber=$course_id empty@test.course +} + +function restore_course_from_url +{ + local url=${1} + + wget $url -O backup_to_restore.mbz + sudo -u www-data moosh --moodle-path=$MOODLE_PATH course-restore backup_to_restore.mbz 1 +} + +function create_2000_test_users_and_enroll_them_in_course +{ + local course_id=${1} + local password=${2} + + # TODO ugly... + sudo -u www-data moosh --moodle-path=$MOODLE_PATH user-create -p $password m_azuretestuser_{1..200} + sudo -u www-data moosh --moodle-path=$MOODLE_PATH user-create -p $password m_azuretestuser_{201..400} + sudo -u www-data moosh --moodle-path=$MOODLE_PATH user-create -p $password m_azuretestuser_{401..600} + sudo -u www-data moosh --moodle-path=$MOODLE_PATH user-create -p $password m_azuretestuser_{601..800} + sudo -u www-data moosh --moodle-path=$MOODLE_PATH user-create -p $password m_azuretestuser_{801..1000} + sudo -u www-data moosh --moodle-path=$MOODLE_PATH user-create -p $password m_azuretestuser_{1001..1200} + sudo -u www-data moosh --moodle-path=$MOODLE_PATH user-create -p $password m_azuretestuser_{1201..1400} + sudo -u www-data moosh --moodle-path=$MOODLE_PATH user-create -p $password m_azuretestuser_{1401..1600} + sudo -u www-data moosh --moodle-path=$MOODLE_PATH user-create -p $password m_azuretestuser_{1601..1800} + sudo -u www-data moosh --moodle-path=$MOODLE_PATH user-create -p $password m_azuretestuser_{1801..2000} + + sudo -u www-data moosh --moodle-path=$MOODLE_PATH course-enrol $course_id m_azuretestuser_{1..200} + sudo -u www-data moosh --moodle-path=$MOODLE_PATH course-enrol $course_id m_azuretestuser_{201..400} + sudo -u www-data moosh --moodle-path=$MOODLE_PATH course-enrol $course_id m_azuretestuser_{401..600} + sudo -u www-data moosh --moodle-path=$MOODLE_PATH course-enrol $course_id m_azuretestuser_{601..800} + sudo -u www-data moosh --moodle-path=$MOODLE_PATH course-enrol $course_id m_azuretestuser_{801..1000} + sudo -u www-data moosh --moodle-path=$MOODLE_PATH course-enrol $course_id m_azuretestuser_{1001..1200} + sudo -u www-data moosh --moodle-path=$MOODLE_PATH course-enrol $course_id m_azuretestuser_{1201..1400} + sudo -u www-data moosh --moodle-path=$MOODLE_PATH course-enrol $course_id m_azuretestuser_{1401..1600} + sudo -u www-data moosh --moodle-path=$MOODLE_PATH course-enrol $course_id m_azuretestuser_{1601..1800} + sudo -u www-data moosh --moodle-path=$MOODLE_PATH course-enrol $course_id m_azuretestuser_{1801..2000} +} + +function hide_course_overview_block_for_jmeter_test +{ + # "myoverview" is the registered name of the "Course overview" block + sudo -u www-data moosh --moodle-path=$MOODLE_PATH block-manage hide myoverview +} + +# TODO hard-coded values... +LOADTEST_BASE_URI=https://raw.githubusercontent.com/Azure/Moodle/master/loadtest +MOODLE_TEST_USER_PASSWORD='testUserP@$$w0rd' + +function setup_test_course_and_users +{ + install_moosh + # TODO hard-coded test course backup location + restore_course_from_url $LOADTEST_BASE_URI/moodle-on-azure-test-course-1.mbz + local course_id=2 # TODO Fix this hard-coded course id #. Should be retrieved from the previous restore_course_from_url output + local password=$MOODLE_TEST_USER_PASSWORD # TODO parameterize + create_2000_test_users_and_enroll_them_in_course $course_id $password + hide_course_overview_block_for_jmeter_test +} + +function run_cmd_on_remote_host +{ + local func_cmd=${1} # E.g., install_moosh or 'delete_course 2' + local ssh_dest=${2} # E.g., azureadmin@10.2.3.4 + local port=${3:-22} # E.g., 2222 + + local cmd="ssh -o 'StrictHostKeyChecking no' -p $port $ssh_dest 'wget $LOADTEST_BASE_URI/loadtest.sh -O loadtest.sh; source loadtest.sh; $func_cmd'" + show_command_to_run $cmd + eval $cmd +} + +function run_simple_test_1_on_resource_group +{ + local resource_group=${1} # Azure resource group where Moodle templates were deployed + local test_threads_count=${2} # E.g., 400, 800, ... + local test_rampup_time_sec=${3} # E.g., 900 (should be long enough for # threads above) + local test_run_time_sec=${4} # E.g., 3600 for 1 hour + local setup_test_course_users_flag=${5} # Run setup_test_course_and_users on moodle_host if nonzero + + sudo apt update; sudo apt install -y jq + local deployment="${resource_group}-deployment" + local output=$(az group deployment show -g $resource_group -n $deployment) + local moodle_host=$(echo $output | jq -r .properties.outputs.siteURL.value) + local db_host=$(echo $output | jq -r .properties.outputs.databaseDNS.value) + local moodle_db_user=$(echo $output | jq -r .properties.outputs.moodleDbUsername.value) + local moodle_db_pass=$(echo $output | jq -r .properties.outputs.moodleDbPassword.value) + local moodle_user_pass=$MOODLE_TEST_USER_PASSWORD # TODO parameterize + + if [ -n "$setup_test_course_users_flag" ]; then + local moodle_controller_ip=$(echo $output | jq -r .properties.outputs.controllerInstanceIP.value) + run_cmd_on_remote_host setup_test_course_and_users azureadmin@${moodle_controller_ip} + fi + + mkdir -p test_outputs + + local prefix="test_outputs/simple_test_1_$(date +%Y%m%d%H%M%S)" + echo $output | jq . > ${prefix}.deployment.json + + export JVM_ARGS="-Xms1024m -Xmx4096m" + 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}" + show_command_to_run $cmd + eval $cmd +} + +function deallocate_services_in_resource_group +{ + local rg=${1} + + # Deallocate VMSS's + local scalesets=$(az vmss list -g $rg --query [].name -o tsv) + for scaleset in $scalesets; do + local cmd="az vmss deallocate -g $rg --name $scaleset" + show_command_to_run $cmd + eval $cmd + done + + # Deallocate VMs + local cmd="az vm deallocate --ids $(az vm list -g $rg --query [].id -o tsv)" + show_command_to_run $cmd + eval $cmd + + # Stopping DBs and redis cache is currently not possible on Azure. +} + +function deploy_run_test1_teardown +{ + local resource_group=${1} + local location=${2} + local template_url=${3} + local parameters_template_file=${4} + local web_server_type=${5} + local web_vm_sku=${6} + local db_server_type=${7} + local db_vcores=${8} + local db_size_gb=${9} + local file_server_type=${10} + local file_server_disk_count=${11} + local file_server_disk_size=${12} + local redis_cache=${13} + local ssh_pub_key=${14} + local test_threads_count=${15} + local test_rampup_time_sec=${16} + local test_run_time_sec=${17} + local delete_resource_group_flag=${18} # Any non-empty string is considered true + + MOODLE_RG_LOCATION=$location + 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 + run_simple_test_1_on_resource_group $resource_group $test_threads_count $test_rampup_time_sec $test_run_time_sec 1 || return 1 + if [ -n "$delete_resource_group_flag" ]; then + az group delete -g $resource_group -y + else + deallocate_services_in_resource_group $resource_group + fi +} + +function check_ssh_agent_and_added_key +{ + ssh-add -l + if [ $? != "0" ]; then + 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." + return 1 + fi +} + +function run_load_test_example +{ + check_ssh_agent_and_added_key || return 1 + + 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 +} + +function run_load_test_postgres +{ + check_ssh_agent_and_added_key || return 1 + + 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 +} diff --git a/loadtest/moodle-on-azure-test-course-1.mbz b/loadtest/moodle-on-azure-test-course-1.mbz new file mode 100644 index 00000000..0435d766 Binary files /dev/null and b/loadtest/moodle-on-azure-test-course-1.mbz differ diff --git a/loadtest/simple-test-1.jmx b/loadtest/simple-test-1.jmx new file mode 100644 index 00000000..636aa2a9 --- /dev/null +++ b/loadtest/simple-test-1.jmx @@ -0,0 +1,2408 @@ + + + + + false + + + + + false + + + + + + + + login_host + ${__P(host, lb-cosbae.southcentralus.cloudapp.azure.com)} + = + + + host + ${__P(host, lb-cosbae.southcentralus.cloudapp.azure.com)} + = + + + threads + ${__P(threads,400)} + = + Default 10 (for a quick GUI run) + + + rampup + ${__P(rampup,900)} + = + Default 120 - 1/15 runtime default rampup + + + runtime + ${__P(runtime,10800)} + = + Default 1800 = 30 minutes = 30*60 seconds + + + default_connect_timeout + 2000 + = + 1.2 seconds to a TCP ack + + + default_response_timeout + 240000 + = + 4 Minutes for the page to respond + + + throughput_rate + ${__P(throughput_rate, 50)} + = + Default 650 should approximate ~ 7k page views / 5 mins cacti sample + + + activity_course_id + ${__P(activity_course_id, 2)} + = + This must match the course ID of the course with performance activities + + + quiz_delay + ${__P(quiz_delay, 100)} + -1 means don't run quiz + = + + + quiz_users + ${__P(quiz_users, 1)} + Number of quiz users to simulate + = + + + groupselect_delay + ${__P(groupselect_delay,-1)} + = + + + groupselect_users + ${__P(groupselect_users, 0)} + = + + + protocol + https + = + + + port + 443 + = + + + moodle_user_pass + ${__P(moodle_user_pass,testUserP@$$w0rd)} + = + + + + + + startnextloop + + false + -1 + + ${threads} + ${rampup} + 1513564238000 + 1513564538000 + true + ${runtime} + 0 + + + + + true + default + + + + + + + + User-Agent + 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) + + + Accept + */* + + + Accept-Language + en-us + + + + + + + + true + + VIEWSTATE + jsessionid + RandomNumber + username + userRandomChoice + sesskey + testBigFileLocation + testBigFileName + discussionid + courseList + xx_outputFromSplit + + + + + + ${__Random(1,2000,ReuseRandomNumber)} + m_azuretestuser_${RandomNumber} + ${RandomNumber} + ${sesskey} + + + + ${__V(course_list_${RandomNumber})} + ${__split(${courseList}, course_id,|)} + + + true + + + + + + + ${host} + ${port} + ${protocol} + + / + GET + true + false + true + false + + Java + ${default_connect_timeout} + ${default_response_timeout} + + + + 1000 + + + + 8000 + 4000 + + + + + + + + true + ${username} + username + = + true + true + + + true + ${moodle_user_pass} + password + = + true + true + + + true + Login + = + true + Login + + + + ${login_host} + ${port} + ${protocol} + + /login/index.php + POST + true + false + true + false + true + + Java + ${default_connect_timeout} + ${default_response_timeout} + + + + + You are logged in as + + 2 + Assertion.response_data + false + + + + + + sesskey + //*[@name='sesskey'][1]/@value + false + true + false + -1 + + + + 1000 + + + + 5000 + 0 + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + false + true + false + false + false + true + 0 + true + true + true + true + true + + + + + + + + true + 5 + + + + + RandomCourse + courseid + + + + ${__Random(1,${course_id_n},ReuseRandomNumber)} + ${activity_course_id} + + + true + + + + + + + false + ${courseid} + id + = + true + true + + + + ${host} + ${port} + ${protocol} + + /course/view.php + GET + true + false + true + false + + Java + ${default_connect_timeout} + ${default_response_timeout} + + + + 1000 + + + + 10000 + 25000 + + + + false + resource_module_id + \/mod\/resource\/view.php\?id=(\d+)" + $1$ + NULL + 0 + + + + + ${__Random(1,10,ReuseRandomNumber)} <= 10 + false + + + + "${resource_module_id}" != "NULL" + false + + + + + + + false + ${resource_module_id} + id + = + true + true + + + + ${host} + ${port} + ${protocol} + + /mod/resource/view.php + GET + true + false + true + false + + Java + + + + + + 1000 + + + + 3000 + 5000 + + + + + + + false + true + false + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + false + false + false + false + false + false + 0 + true + true + + + + + + + + true + 3 + + + + 20% of the time + ${__Random(1,10,ReuseRandomNumber)} <= 10 + false + + + + + + + false + ${activity_course_id} + id + = + true + true + + + + ${host} + ${port} + ${protocol} + + /course/view.php + GET + true + false + true + false + + Java + + + + + + + Performance Testing Forum + + 2 + Assertion.response_data + false + + + + + false + coursetopics_html + Performance Testing Peak Quiz.*(<div><div class="mod-indent-outer">.*?<a .*Performance Testing Forum 0.*Performance Testing Forum 9.*?<\/a>) + $1$ + NULL + + + + + NULL + moduleid + substring-after(//*[contains(text(),'Performance Testing Forum ${__Random(0,9,ReuseRandomNumber)}')]/../@href,'id=') + false + true + false + true + false + 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 + true + variable + coursetopics_html + -1 + + + + NULL + moduleid + substring-after(//*/div[@class="activityinstance"]/*/*[contains(text(),'Performance Testing Forum ${__Random(0,9,ReuseRandomNumber)}')]/../@href,'id=') + false + true + false + true + false + 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 + true + -1 + + + + 1000 + + + + 10000 + 30000 + + + + + + + + false + ${moduleid} + id + = + true + true + + + + ${host} + ${port} + ${protocol} + + /mod/forum/view.php + GET + true + false + true + false + + Java + + + + + + + Performance Testing Forum + Add a new discussion topic + + Assertion.response_data + false + 2 + + + + + false + forumheaderlist + (?s)(<table .*?class="forumheaderlist".*?<\/table>) + $1$ + NULL + + + + + NULL + discuss_id + substring-after(//*/div[@class="activityinstance"]/*/*[contains(text(),'Performance Testing Forum ${__Random(0,9,ReuseRandomNumber)}')]/../@href,'id=') + false + true + false + true + false + 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 + true + variable + forumheaderlist + -1 + + + + false + forumid + name="forum" value="(\d+)" + $1$ + NULL + + + + + + forumid + //*[@name='forum'][1]/@value + false + true + false + true + true + false + -1 + + + + false + discuss_id + \/mod\/forum\/discuss.php\?d=(\d+) + $1$ + NULL + 0 + + + + false + discuss_title + \/mod\/forum\/discuss.php\?d=${discuss_id}">(.*?)</a> + $1$ + NULL + 1 + + + + 1000 + + + + 25000 + 20000 + + + + + false + true + false + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + false + false + false + false + false + false + 0 + true + true + + + + + + + "${discuss_id}" != "NULL" && ${discuss_id} > 0 + false + + + + ${__Random(1,10,ReuseRandomNumber)} <= 10 + false + + + + + + + false + ${discuss_id} + d + = + true + true + + + + ${host} + ${port} + ${protocol} + + /mod/forum/discuss.php + GET + true + false + true + false + + Java + + + View a forum discussion thread + + + + + ${discuss_title} + My Course list is + + Assertion.response_data + false + 2 + + + + + false + forumid + name="forum" value="(\d+)" + $1$ + NULL + + + + + + forumid + //*[@name='forum'][1]/@value + false + true + false + true + true + false + -1 + + + + 1000 + + + + 3000 + 12000 + + + + + + + + false + ${discuss_id} + d + = + true + true + + + + ${host} + ${port} + ${protocol} + + /mod/forum/discuss.php + GET + true + false + true + false + + Java + + + View a forum discussion thread + + + + + ${discuss_title} + My Course list is + + Assertion.response_data + false + 2 + + + + + false + forumid + name="forum" value="(\d+)" + $1$ + NULL + + + + + + forumid + //*[@name='forum'][1]/@value + false + true + false + true + true + false + -1 + + + + 1000 + + + + 10000 + 4000 + + + + + + + Only create a forum post (10% of the time a forum is viewed) + ${__Random(1,20,ReuseRandomNumber)} <= 20 + false + + + + + + + false + ${forumid} + forum + = + true + true + + + + ${host} + ${port} + ${protocol} + + /mod/forum/post.php + GET + true + false + true + false + + Java + + + + + + + Your new discussion topic + + Assertion.response_data + false + 2 + + + + + + discussionid + //*[@name='discussion'][1]/@value + false + true + false + true + true + false + -1 + + + + false + discussionid + name="discussion".*?value="([\d]+)" + $1$ + NULL + 1 + + + + + parentid + //*[@name='parent'][1]/@value + false + true + false + true + true + false + -1 + + + + false + parentid + name="parent".*?value="([\d]+)" + $1$ + NULL + 1 + + + + + userid + //*[@name='userid'][1]/@value + false + true + false + true + true + false + -1 + + + + false + userid + name="userid".*?value="([\d]+)" + $1$ + NULL + 1 + + + + + groupid + //*[@name='groupid'][1]/@value + false + true + false + true + true + false + -1 + + + + false + groupid + name="groupid".*?value="([\d]+)" + $1$ + NULL + 1 + + + + + editid + //*[@name='edit'][1]/@value + false + true + false + true + true + false + -1 + + + + false + editid + name="edit".*?value="([\d]+)" + $1$ + NULL + 1 + + + + + attachmentid + //*[@name='attachment'][1]/@value + false + true + false + true + true + false + -1 + + + + false + attachmentid + name="attachment".*?value="([\d]+)" + $1$ + NULL + 1 + + + + 1000 + + + + 4000 + 3000 + + + + + messageitemid + //*[@name='message[itemid]'][1]/@value + false + true + false + true + true + false + -1 + + + + false + messageitemid + name="message\[itemid\]".*?value="([\d]+)" + $1$ + NULL + 1 + + + + + false + true + false + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + false + false + false + false + false + false + 0 + true + true + + + + + + + + + + true + 1 + _qf__mod_forum_post_form + = + true + true + + + false + ${attachmentid} + = + true + attachments + + + true + ${activity_course_id} + course + = + true + true + + + true + ${discussionid} + discussion + = + true + true + + + true + ${editid} + edit + = + true + true + + + true + ${forumid} + forum + = + true + true + + + true + ${groupid} + groupid + = + true + true + + + false + 1 + = + true + message[format] + + + false + ${messageitemid} + = + true + message[itemid] + + + true + <p>My Course list is ${courseList}</p> + message[text] + = + true + true + + + true + ${parentid} + parent + = + true + true + + + true + 0 + reply + = + true + true + + + true + ${sesskey} + sesskey + = + true + true + + + true + ${username} - ${RandomNumber} + subject + = + true + true + + + false + Post to forum + = + true + submitbutton + + + true + 0 + timestart + = + true + true + + + true + 0 + timeend + = + true + true + + + true + ${userid} + userid + = + true + true + + + + ${host} + ${port} + ${protocol} + + /mod/forum/post.php + POST + true + false + true + true + + Java + + + + + + + Your post was successfully + + 2 + Assertion.response_data + false + + + + + 1000 + + + + + + + + false + ${moduleid} + id + = + true + true + + + + ${host} + ${port} + ${protocol} + + /mod/forum/view.php + GET + true + false + true + false + + Java + + + + + + + Performance Testing Forum + + Assertion.response_data + false + 2 + + + + + + discussionid + substring-after(//*/a[contains(text(),'${username} - ${RandomNumber}')][1]/@href,'=') + false + true + false + true + true + false + -1 + + + + false + discussionid + name="message\[itemid\]".*?value="([\d]+)" + $1$ + NULL + 1 + + + + 1000 + + + + 4000 + 3000 + + + + + false + true + false + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + false + false + false + false + false + false + 0 + true + true + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + true + + + + + + + + + ${__Random(1,10,ReuseRandomNumber)} <= 10 + false + 30% of the time, do the quiz + + + + + + + false + ${activity_course_id} + id + = + true + true + + + + ${host} + ${port} + ${protocol} + + /course/view.php + GET + true + false + true + false + + Java + + + + + + + Performance Testing Regular Quiz + + 2 + Assertion.response_data + false + + + + + false + classtopics_html + (?s)(<ul class="topics".*?)<\/div>.*?<\/div>.*?<\/div>.*?<\/div>.*?<\/div>.*?<div id="region-pre" + $1$ + NULL + + + + + false + html_chunk_perfquiz + (.{0,250}Performance Testing Regular Quiz) + $1$ + NULL + + + + + false + moduleid + \/mod\/quiz\/view\.php\?id=(\d+) + $1$ + NULL + + variable + html_chunk_perfquiz + + + + NULL + moduleid + substring-after(//*[contains(text(),'Performance Testing Regular Quiz')]/../@href,'id=') + false + true + false + true + false + true + variable + classtopics_html + -1 + + + + + moduleid + substring-after(//*/a/*[contains(text(),'Performance Testing Regular Quiz')]/../@href,'=') + false + true + false + true + true + false + -1 + + + + 4000 + 3000 + + + + 1000 + + + + + + + + false + ${moduleid} + id + = + true + true + + + + ${host} + ${port} + ${protocol} + + /mod/quiz/view.php + GET + true + false + true + false + + Java + + + + + + + Performance Testing Regular Quiz + + Assertion.response_data + false + 2 + + + + + + quizid + //*/form[@action="https://${host}/mod/quiz/startattempt.php"]//*/input[@name='cmid'][1]/@value + false + true + false + true + true + false + -1 + + + + 10000 + 3000 + + + + 1000 + + + + + + + + false + ${moduleid} + cmid + = + true + true + + + false + ${sesskey} + = + true + sesskey + + + + ${host} + ${port} + ${protocol} + + /mod/quiz/startattempt.php + POST + true + false + true + false + + Java + + + + + + + What's your username + Is the LMS Performing acceptably? + What is 2 \+ 2\? + + Assertion.response_data + false + 2 + + + + + 10000 + 3000 + + + + 1000 + + + + + attemptid + //*/form[@action="https://${host}/mod/quiz/processattempt.php"]//*/input[@name='attempt'][1]/@value + false + true + false + true + true + false + -1 + + + + false + attemptid + \/mod\/quiz\/attempt\.php\?attempt=(\d+)">Continue<\/a> + $1$ + NULL + + children + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + false + true + false + false + false + true + 0 + true + true + true + true + true + + + + + + + + + + + false + 0 + = + true + q${attemptid}:1_:flagged + + + false + 1 + = + true + q${attemptid}:1_:sequencecheck + + + false + ${username}@${user_type} + = + true + q${attemptid}:1_answer + + + false + 0 + = + true + q${attemptid}:2_:flagged + + + false + 1 + = + true + q${attemptid}:2_:sequencecheck + + + false + ${__Random(0,1)} + = + true + q${attemptid}:2_answer + + + false + 0 + = + true + q${attemptid}:3_:flagged + + + false + 1 + = + true + q${attemptid}:3_:sequencecheck + + + false + ${__Random(3,5)} + = + true + q${attemptid}:3_answer + + + false + Next + = + true + next + + + false + ${attemptid} + = + true + attempt + + + false + 0 + = + true + thispage + + + false + -1 + = + true + nextpage + + + false + 0 + = + true + timeup + + + false + ${sesskey} + = + true + sesskey + + + false + + = + true + scrollpos + + + false + 1,2,3 + = + true + slots + + + + ${host} + ${port} + ${protocol} + utf-8 + /mod/quiz/processattempt.php + POST + true + false + true + true + + Java + + + + + + + Review of attempt + + 2 + Assertion.response_data + false + + + + + 25000 + 15000 + + + + 1000 + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + false + true + false + false + false + true + 0 + true + true + true + true + true + + + + + + + + + + + false + ${sesskey} + = + true + sesskey + + + false + + = + true + slots + + + false + 0 + = + true + timeup + + + false + 1 + = + true + finishattempt + + + false + ${attemptid} + = + true + attempt + + + + ${host} + ${port} + ${protocol} + utf-8 + /mod/quiz/processattempt.php + POST + true + false + true + true + + Java + + + + + + + Review of attempt + + 2 + Assertion.response_data + false + + + + + 4000 + 3000 + + + + 1000 + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + false + true + false + false + false + true + 0 + true + true + true + true + true + + + + + + + + + + + false + ${moduleid} + = + true + id + + + + ${host} + ${port} + ${protocol} + + /mod/quiz/view.php + GET + true + false + true + true + + Java + + + + + + + Summary of your previous attempts + + 2 + Assertion.response_data + false + + + + + + Highest grade + + 2 + Assertion.response_data + false + + + + + 4000 + 3000 + + + + 1000 + + + + + false + true + false + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + true + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + false + false + false + false + false + false + 0 + true + true + + + + + + + 20000 + 20000 + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + false + true + false + false + false + true + 0 + true + true + true + true + true + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + false + true + false + false + false + true + 0 + true + true + true + true + true + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + false + true + false + false + false + true + 0 + true + true + true + true + true + + + + 1000 + false + + + + + false + false + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + false + true + false + false + false + true + 0 + true + true + true + true + true + + + + 1000 + false + + + + + false + false + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + false + true + false + false + false + true + 0 + true + true + true + true + true + + + + 500 + false + + + + + false + false + + + + + + + diff --git a/loadtest/simple-test-2.jmx b/loadtest/simple-test-2.jmx new file mode 100644 index 00000000..51424f14 --- /dev/null +++ b/loadtest/simple-test-2.jmx @@ -0,0 +1,2704 @@ + + + + + false + + + + + false + + + + + + + + login_host + ${__P(host, lb-hivmol.southcentralus.cloudapp.azure.com)} + = + + + host + ${__P(host, lb-hivmol.southcentralus.cloudapp.azure.com)} + = + + + threads + ${__P(threads,100)} + = + Default 10 (for a quick GUI run) + + + rampup + ${__P(rampup,1200)} + = + Default 120 - 1/15 runtime default rampup + + + runtime + ${__P(runtime,86400)} + = + Default 1800 = 30 minutes = 30*60 seconds + + + default_connect_timeout + 2000 + = + 1.2 seconds to a TCP ack + + + default_response_timeout + 240000 + = + 4 Minutes for the page to respond + + + throughput_rate + ${__P(throughput_rate, 50)} + = + Default 650 should approximate ~ 7k page views / 5 mins cacti sample + + + activity_course_id + ${__P(activity_course_id, 2)} + = + This must match the course ID of the course with performance activities + + + quiz_delay + ${__P(quiz_delay, 100)} + -1 means don't run quiz + = + + + quiz_users + ${__P(quiz_users, 1)} + Number of quiz users to simulate + = + + + groupselect_delay + ${__P(groupselect_delay,-1)} + = + + + groupselect_users + ${__P(groupselect_users, 0)} + = + + + protocol + https + = + + + port + 443 + = + + + moodle_user_pass + ${__P(moodle_user_pass,testUserP@$$w0rd)} + = + + + + + + startnextloop + + false + -1 + + ${threads} + ${rampup} + 1513564238000 + 1513564538000 + true + ${runtime} + 0 + + + + + true + default + + + + + + + + User-Agent + 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) + + + Accept + */* + + + Accept-Language + en-us + + + + + + + + true + + VIEWSTATE + jsessionid + RandomNumber + username + userRandomChoice + sesskey + discussionid + courseList + xx_outputFromSplit + + + + + + ${__Random(1,2000,ReuseRandomNumber)} + m_azuretestuser_${RandomNumber} + ${RandomNumber} + ${sesskey} + + ${__V(course_list_${RandomNumber})} + ${__split(${courseList}, course_id,|)} + + + true + + + + + + + ${host} + ${port} + ${protocol} + + / + GET + true + false + true + false + + Java + ${default_connect_timeout} + ${default_response_timeout} + + + + 1000 + + + + 8000 + 4000 + + + + + + + + true + ${username} + username + = + true + true + + + true + ${moodle_user_pass} + password + = + true + true + + + true + Login + = + true + Login + + + + ${login_host} + ${port} + ${protocol} + + /login/index.php + POST + true + false + true + false + true + + Java + ${default_connect_timeout} + ${default_response_timeout} + + + + + You are logged in as + + 2 + Assertion.response_data + false + + + + + + sesskey + //*[@name='sesskey'][1]/@value + false + true + false + -1 + + + + 1000 + + + + 5000 + 0 + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + false + true + false + false + false + true + 0 + true + true + true + true + true + + + + + + + + true + 5 + + + + + RandomCourse + courseid + + + + ${__Random(1,${course_id_n},ReuseRandomNumber)} + ${activity_course_id} + + + true + + + + + + + false + ${courseid} + id + = + true + true + + + + ${host} + ${port} + ${protocol} + + /course/view.php + GET + true + false + true + false + + Java + ${default_connect_timeout} + ${default_response_timeout} + + + + 1000 + + + + 10000 + 25000 + + + + false + resource_module_id + \/mod\/resource\/view.php\?id=(\d+)" + $1$ + NULL + 0 + + + + + ${__Random(1,10,ReuseRandomNumber)} <= 10 + false + + + + "${resource_module_id}" != "NULL" + false + + + + + + + false + ${resource_module_id} + id + = + true + true + + + + ${host} + ${port} + ${protocol} + + /mod/resource/view.php + GET + true + false + true + false + + Java + + + + + + 1000 + + + + 3000 + 5000 + + + + + + + ${__Random(1,10,ReuseRandomNumber)} <= 10 + false + + + + + + + ${host} + ${port} + ${protocol} + + /user/files.php + GET + true + false + true + false + + Java + + + + + + false + upload_sesskey + name="sesskey".*?value="([\w]+)" + $1$ + NULL + 1 + + + + false + upload_filemanager_id + "itemid":([\d]+) + $1$ + NULL + 1 + + + + false + upload_clientid + "client_id":"([\w]+)" + $1$ + NULL + 1 + + + + false + upload_ctxid + ctx_id=([\d]+) + $1$ + NULL + 1 + + + + 1000 + + + + 3000 + 5000 + + + + + + + + ${upload_test_filename} + repo_upload_file + application/octet-stream + + + + + + + false + + = + true + title + + + false + ${username} + = + true + author + + + false + allrightsreserved + = + true + license + + + false + ${upload_filemanager_id} + = + true + itemid + + + false + 8 + = + true + repo_id + + + false + + = + true + p + + + false + + = + true + page + + + false + filemanager + = + true + env + + + false + ${upload_sesskey} + = + true + sesskey + + + false + ${upload_clientid} + = + true + client_id + + + false + ${upload_filemanager_id} + = + true + itemid + + + false + -1 + = + true + maxbytes + + + false + -1 + = + true + areamaxbytes + + + false + ${upload_ctxid} + = + true + ctx_id + + + false + / + = + true + savepath + + + + ${host} + ${port} + ${protocol} + + /repository/repository_ajax.php?action=upload + POST + true + false + true + true + + Java + + + + + + 1000 + + + + 3000 + 5000 + + + + false + + + import org.apache.commons.io.FileUtils; +import org.apache.commons.lang3.RandomStringUtils; +import org.apache.commons.lang3.RandomUtils; + +// Create a temp file +File myFile = File.createTempFile("upload-", "" ); + +// Generate Random length string and write to file +FileUtils.writeStringToFile(myFile, RandomStringUtils.random( RandomUtils.nextInt( 500000, 5000000 ) ), "UTF-8" ); + +// Store file name in variable. +vars.put( "upload_test_filename", myFile.getCanonicalPath() ); + + + + + + false + import org.apache.commons.io.FileUtils; +// Delete file and do not throw error +FileUtils.deleteQuietly(new File( vars.get("upload_test_filename"))); + + + + + + + + true + ${protocol}://${host}/user/files.php + = + true + returnurl + + + false + ${upload_sesskey} + = + true + sesskey + + + false + 1 + = + true + _qf__user_files_form + + + false + ${upload_filemanager_id} + = + true + files_filemanager + + + false + Save changes + = + true + submitbutton + + + + ${host} + ${port} + ${protocol} + + /user/files.php + POST + true + false + true + false + + Java + + + + + + 1000 + + + + 3000 + 5000 + + + + + + false + true + false + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + false + false + false + false + false + false + 0 + true + true + + + + + + + + true + 3 + + + + 20% of the time + ${__Random(1,10,ReuseRandomNumber)} <= 10 + false + + + + + + + false + ${activity_course_id} + id + = + true + true + + + + ${host} + ${port} + ${protocol} + + /course/view.php + GET + true + false + true + false + + Java + + + + + + + Performance Testing Forum + + 2 + Assertion.response_data + false + + + + + false + coursetopics_html + Performance Testing Peak Quiz.*(<div><div class="mod-indent-outer">.*?<a .*Performance Testing Forum 0.*Performance Testing Forum 9.*?<\/a>) + $1$ + NULL + + + + + NULL + moduleid + substring-after(//*[contains(text(),'Performance Testing Forum ${__Random(0,9,ReuseRandomNumber)}')]/../@href,'id=') + false + true + false + true + false + 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 + true + variable + coursetopics_html + -1 + + + + NULL + moduleid + substring-after(//*/div[@class="activityinstance"]/*/*[contains(text(),'Performance Testing Forum ${__Random(0,9,ReuseRandomNumber)}')]/../@href,'id=') + false + true + false + true + false + 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 + true + -1 + + + + 1000 + + + + 10000 + 30000 + + + + + + + + false + ${moduleid} + id + = + true + true + + + + ${host} + ${port} + ${protocol} + + /mod/forum/view.php + GET + true + false + true + false + + Java + + + + + + + Performance Testing Forum + Add a new discussion topic + + Assertion.response_data + false + 2 + + + + + false + forumheaderlist + (?s)(<table .*?class="forumheaderlist".*?<\/table>) + $1$ + NULL + + + + + NULL + discuss_id + substring-after(//*/div[@class="activityinstance"]/*/*[contains(text(),'Performance Testing Forum ${__Random(0,9,ReuseRandomNumber)}')]/../@href,'id=') + false + true + false + true + false + 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 + true + variable + forumheaderlist + -1 + + + + false + forumid + name="forum" value="(\d+)" + $1$ + NULL + + + + + + forumid + //*[@name='forum'][1]/@value + false + true + false + true + true + false + -1 + + + + false + discuss_id + \/mod\/forum\/discuss.php\?d=(\d+) + $1$ + NULL + 0 + + + + false + discuss_title + \/mod\/forum\/discuss.php\?d=${discuss_id}">(.*?)</a> + $1$ + NULL + 1 + + + + 1000 + + + + 25000 + 20000 + + + + + false + true + false + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + false + false + false + false + false + false + 0 + true + true + + + + + + + "${discuss_id}" != "NULL" && ${discuss_id} > 0 + false + + + + ${__Random(1,10,ReuseRandomNumber)} <= 10 + false + + + + + + + false + ${discuss_id} + d + = + true + true + + + + ${host} + ${port} + ${protocol} + + /mod/forum/discuss.php + GET + true + false + true + false + + Java + + + View a forum discussion thread + + + + + ${discuss_title} + My Course list is + + Assertion.response_data + false + 2 + + + + + false + forumid + name="forum" value="(\d+)" + $1$ + NULL + + + + + + forumid + //*[@name='forum'][1]/@value + false + true + false + true + true + false + -1 + + + + 1000 + + + + 3000 + 12000 + + + + + + + + false + ${discuss_id} + d + = + true + true + + + + ${host} + ${port} + ${protocol} + + /mod/forum/discuss.php + GET + true + false + true + false + + Java + + + View a forum discussion thread + + + + + ${discuss_title} + My Course list is + + Assertion.response_data + false + 2 + + + + + false + forumid + name="forum" value="(\d+)" + $1$ + NULL + + + + + + forumid + //*[@name='forum'][1]/@value + false + true + false + true + true + false + -1 + + + + 1000 + + + + 10000 + 4000 + + + + + + + Only create a forum post (10% of the time a forum is viewed) + ${__Random(1,20,ReuseRandomNumber)} <= 20 + false + + + + + + + false + ${forumid} + forum + = + true + true + + + + ${host} + ${port} + ${protocol} + + /mod/forum/post.php + GET + true + false + true + false + + Java + + + + + + + Your new discussion topic + + Assertion.response_data + false + 2 + + + + + + discussionid + //*[@name='discussion'][1]/@value + false + true + false + true + true + false + -1 + + + + false + discussionid + name="discussion".*?value="([\d]+)" + $1$ + NULL + 1 + + + + + parentid + //*[@name='parent'][1]/@value + false + true + false + true + true + false + -1 + + + + false + parentid + name="parent".*?value="([\d]+)" + $1$ + NULL + 1 + + + + + userid + //*[@name='userid'][1]/@value + false + true + false + true + true + false + -1 + + + + false + userid + name="userid".*?value="([\d]+)" + $1$ + NULL + 1 + + + + + groupid + //*[@name='groupid'][1]/@value + false + true + false + true + true + false + -1 + + + + false + groupid + name="groupid".*?value="([\d]+)" + $1$ + NULL + 1 + + + + + editid + //*[@name='edit'][1]/@value + false + true + false + true + true + false + -1 + + + + false + editid + name="edit".*?value="([\d]+)" + $1$ + NULL + 1 + + + + + attachmentid + //*[@name='attachment'][1]/@value + false + true + false + true + true + false + -1 + + + + false + attachmentid + value="([\d]+)".*?name="attachments" + $1$ + NULL + 1 + + + + false + ctxid + ctx_id=([\d]+) + $1$ + NULL + 1 + + + + false + clientid + "client_id":"([0-9a-f]+)" + $1$ + NULL + 1 + + + + 1000 + + + + 4000 + 3000 + + + + + messageitemid + //*[@name='message[itemid]'][1]/@value + false + true + false + true + true + false + -1 + + + + false + messageitemid + name="message\[itemid\]".*?value="([\d]+)" + $1$ + NULL + 1 + + + + + + + + true + ${activity_course_id} + course + = + true + true + + + true + ${forumid} + forum + = + true + true + + + true + ${discussionid} + discussion + = + true + true + + + true + ${parentid} + parent + = + true + true + + + true + ${groupid} + groupid + = + true + true + + + true + ${editid} + edit + = + true + true + + + true + 0 + reply + = + true + true + + + true + ${sesskey} + sesskey + = + true + true + + + true + 1 + _qf__mod_forum_post_form + = + true + true + + + false + 1 + = + true + mform_isexpanded_id_general + + + false + 0 + = + true + mform_isexpanded_id_displayperiod + + + false + 0 + = + true + mform_isexpanded_id_tagshdr + + + true + ${username} - ${RandomNumber} + subject + = + true + true + + + true + <p>My Course list is ${courseList}</p> + message[text] + = + true + true + + + false + 1 + = + true + message[format] + + + false + ${messageitemid} + = + true + message[itemid] + + + false + 1 + = + true + discussionsubscribe + + + false + ${attachmentid} + = + true + attachments + + + false + _qf__force_multiselect_submission + = + true + tags + + + false + Post to forum + = + true + submitbutton + + + + ${host} + ${port} + ${protocol} + + /mod/forum/post.php + POST + true + false + true + true + + Java + + + + + + + Your post was successfully + + 2 + Assertion.response_data + false + + + + + 1000 + + + + + + + + false + ${moduleid} + id + = + true + true + + + + ${host} + ${port} + ${protocol} + + /mod/forum/view.php + GET + true + false + true + false + + Java + + + + + + + Performance Testing Forum + + Assertion.response_data + false + 2 + + + + + + discussionid + substring-after(//*/a[contains(text(),'${username} - ${RandomNumber}')][1]/@href,'=') + false + true + false + true + true + false + -1 + + + + false + discussionid + name="message\[itemid\]".*?value="([\d]+)" + $1$ + NULL + 1 + + + + 1000 + + + + 4000 + 3000 + + + + + false + true + false + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + false + false + false + false + false + false + 0 + true + true + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + true + + + + + + + + + ${__Random(1,10,ReuseRandomNumber)} <= 10 + false + 30% of the time, do the quiz + + + + + + + false + ${activity_course_id} + id + = + true + true + + + + ${host} + ${port} + ${protocol} + + /course/view.php + GET + true + false + true + false + + Java + + + + + + + Performance Testing Regular Quiz + + 2 + Assertion.response_data + false + + + + + false + classtopics_html + (?s)(<ul class="topics".*?)<\/div>.*?<\/div>.*?<\/div>.*?<\/div>.*?<\/div>.*?<div id="region-pre" + $1$ + NULL + + + + + false + html_chunk_perfquiz + (.{0,250}Performance Testing Regular Quiz) + $1$ + NULL + + + + + false + moduleid + \/mod\/quiz\/view\.php\?id=(\d+) + $1$ + NULL + + variable + html_chunk_perfquiz + + + + NULL + moduleid + substring-after(//*[contains(text(),'Performance Testing Regular Quiz')]/../@href,'id=') + false + true + false + true + false + true + variable + classtopics_html + -1 + + + + + moduleid + substring-after(//*/a/*[contains(text(),'Performance Testing Regular Quiz')]/../@href,'=') + false + true + false + true + true + false + -1 + + + + 4000 + 3000 + + + + 1000 + + + + + + + + false + ${moduleid} + id + = + true + true + + + + ${host} + ${port} + ${protocol} + + /mod/quiz/view.php + GET + true + false + true + false + + Java + + + + + + + Performance Testing Regular Quiz + + Assertion.response_data + false + 2 + + + + + + quizid + //*/form[@action="https://${host}/mod/quiz/startattempt.php"]//*/input[@name='cmid'][1]/@value + false + true + false + true + true + false + -1 + + + + 10000 + 3000 + + + + 1000 + + + + + + + + false + ${moduleid} + cmid + = + true + true + + + false + ${sesskey} + = + true + sesskey + + + + ${host} + ${port} + ${protocol} + + /mod/quiz/startattempt.php + POST + true + false + true + false + + Java + + + + + + + What's your username + Is the LMS Performing acceptably? + What is 2 \+ 2\? + + Assertion.response_data + false + 2 + + + + + 10000 + 3000 + + + + 1000 + + + + + attemptid + //*/form[@action="https://${host}/mod/quiz/processattempt.php"]//*/input[@name='attempt'][1]/@value + false + true + false + true + true + false + -1 + + + + false + attemptid + \/mod\/quiz\/attempt\.php\?attempt=(\d+)">Continue<\/a> + $1$ + NULL + + children + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + false + true + false + false + false + true + 0 + true + true + true + true + true + + + + + + + + + + + false + 0 + = + true + q${attemptid}:1_:flagged + + + false + 1 + = + true + q${attemptid}:1_:sequencecheck + + + false + ${username}@${user_type} + = + true + q${attemptid}:1_answer + + + false + 0 + = + true + q${attemptid}:2_:flagged + + + false + 1 + = + true + q${attemptid}:2_:sequencecheck + + + false + ${__Random(0,1)} + = + true + q${attemptid}:2_answer + + + false + 0 + = + true + q${attemptid}:3_:flagged + + + false + 1 + = + true + q${attemptid}:3_:sequencecheck + + + false + ${__Random(3,5)} + = + true + q${attemptid}:3_answer + + + false + Next + = + true + next + + + false + ${attemptid} + = + true + attempt + + + false + 0 + = + true + thispage + + + false + -1 + = + true + nextpage + + + false + 0 + = + true + timeup + + + false + ${sesskey} + = + true + sesskey + + + false + + = + true + scrollpos + + + false + 1,2,3 + = + true + slots + + + + ${host} + ${port} + ${protocol} + utf-8 + /mod/quiz/processattempt.php + POST + true + false + true + true + + Java + + + + + + + Review of attempt + + 2 + Assertion.response_data + false + + + + + 25000 + 15000 + + + + 1000 + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + false + true + false + false + false + true + 0 + true + true + true + true + true + + + + + + + + + + + false + ${sesskey} + = + true + sesskey + + + false + + = + true + slots + + + false + 0 + = + true + timeup + + + false + 1 + = + true + finishattempt + + + false + ${attemptid} + = + true + attempt + + + + ${host} + ${port} + ${protocol} + utf-8 + /mod/quiz/processattempt.php + POST + true + false + true + true + + Java + + + + + + + Review of attempt + + 2 + Assertion.response_data + false + + + + + 4000 + 3000 + + + + 1000 + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + false + true + false + false + false + true + 0 + true + true + true + true + true + + + + + + + + + + + false + ${moduleid} + = + true + id + + + + ${host} + ${port} + ${protocol} + + /mod/quiz/view.php + GET + true + false + true + true + + Java + + + + + + + Summary of your previous attempts + + 2 + Assertion.response_data + false + + + + + + Highest grade + + 2 + Assertion.response_data + false + + + + + 4000 + 3000 + + + + 1000 + + + + + false + true + false + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + true + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + false + false + false + false + false + false + 0 + true + true + + + + + + + 20000 + 20000 + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + false + true + false + false + false + true + 0 + true + true + true + true + true + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + false + true + false + false + false + true + 0 + true + true + true + true + true + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + false + true + false + false + false + true + 0 + true + true + true + true + true + + + + 1000 + false + + + + + false + false + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + false + true + false + false + false + true + 0 + true + true + true + true + true + + + + 1000 + false + + + + + false + false + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + false + true + false + false + false + true + 0 + true + true + true + true + true + + + + 500 + false + + + + + false + false + + + + + + + diff --git a/loadtest/time-gated-exam-test-dist-slaves.jmx b/loadtest/time-gated-exam-test-dist-slaves.jmx new file mode 100644 index 00000000..5075f0c9 --- /dev/null +++ b/loadtest/time-gated-exam-test-dist-slaves.jmx @@ -0,0 +1,1468 @@ + + + + + false + + + + + false + + + + + + + + login_host + ${__P(host, lb-kkvgra.southcentralus.cloudapp.azure.com)} + = + + + host + ${__P(host, lb-kkvgra.southcentralus.cloudapp.azure.com)} + = + + + threads + ${__P(threads,250)} + = + Default 10 (for a quick GUI run) + + + rampup + ${__P(rampup,250)} + = + Default 120 - 1/15 runtime default rampup + + + runtime + ${__P(runtime,3600)} + = + Default 1800 = 30 minutes = 30*60 seconds + + + default_connect_timeout + 2000 + = + 1.2 seconds to a TCP ack + + + default_response_timeout + 240000 + = + 4 Minutes for the page to respond + + + throughput_rate + ${__P(throughput_rate, 50)} + = + Default 650 should approximate ~ 7k page views / 5 mins cacti sample + + + activity_course_id + ${__P(activity_course_id, 2)} + = + This must match the course ID of the course with performance activities + + + quiz_delay + ${__P(quiz_delay, 100)} + -1 means don't run quiz + = + + + quiz_users + ${__P(quiz_users, 1)} + Number of quiz users to simulate + = + + + groupselect_delay + ${__P(groupselect_delay,-1)} + = + + + groupselect_users + ${__P(groupselect_users, 0)} + = + + + protocol + https + = + + + port + 443 + = + + + moodle_user_pass + ${__P(moodle_user_pass,testUserP@$$w0rd)} + = + + + + + + startnextloop + + false + 1 + + ${threads} + ${rampup} + 1513564238000 + 1513564538000 + true + ${runtime} + 0 + + + + + true + default + + + + + + + + User-Agent + 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) + + + Accept + */* + + + Accept-Language + en-us + + + + + + + + true + + VIEWSTATE + jsessionid + RandomNumber + userNum + username + userRandomChoice + sesskey + testBigFileLocation + testBigFileName + discussionid + courseList + xx_outputFromSplit + + + + + + ${__Random(1,2000,ReuseRandomNumber)} + ${__javaScript(Math.floor(ctx.getThreadGroup().getNumThreads()*props.getProperty("slaveNum"\,"0")+ctx.getThreadNum()+1).toFixed(0))} + m_azuretestuser_${userNum} + ${RandomNumber} + ${sesskey} + + + + ${__V(course_list_${RandomNumber})} + ${__split(${courseList}, course_id,|)} + + + true + + + + + + + ${host} + ${port} + ${protocol} + + / + GET + true + false + true + false + + Java + ${default_connect_timeout} + ${default_response_timeout} + + + + 1000 + + + + 8000 + 4000 + + + + + + + + true + ${username} + username + = + true + true + + + true + ${moodle_user_pass} + password + = + true + true + + + true + Login + = + true + Login + + + + ${login_host} + ${port} + ${protocol} + + /login/index.php + POST + true + false + true + false + true + + Java + ${default_connect_timeout} + ${default_response_timeout} + + + + + You are logged in as + + 2 + Assertion.response_data + false + + + + + + sesskey + //*[@name='sesskey'][1]/@value + false + true + false + -1 + + + + 1000 + + + + 5000 + 0 + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + false + true + false + false + false + true + 0 + true + true + true + true + true + + + + + + + + true + 10 + + + + + + + + + false + ${activity_course_id} + id + = + true + true + + + + ${host} + ${port} + ${protocol} + + /course/view.php + GET + true + false + true + false + + Java + + + + + + + Performance Testing Regular Quiz + + 2 + Assertion.response_data + true + + + + + false + classtopics_html + (?s)(<ul class="topics".*?)<\/div>.*?<\/div>.*?<\/div>.*?<\/div>.*?<\/div>.*?<div id="region-pre" + $1$ + NULL + + + + + false + html_chunk_perfquiz + (.{0,250}Performance Testing Regular Quiz) + $1$ + NULL + + + + + false + moduleid + \/mod\/quiz\/view\.php\?id=(\d+) + $1$ + NULL + + variable + html_chunk_perfquiz + + + + NULL + moduleid + substring-after(//*[contains(text(),'Performance Testing Regular Quiz')]/../@href,'id=') + false + true + false + true + false + true + variable + classtopics_html + -1 + + + + + moduleid + substring-after(//*/a/*[contains(text(),'Performance Testing Regular Quiz')]/../@href,'=') + false + true + false + true + true + false + -1 + + + + 2000 + 1000 + + + + 1000 + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + false + true + false + false + false + true + 0 + true + true + true + true + true + + + + + + + + ${__javaScript("${quiz_opened}" != "This quiz opened at")} + + + + + + + false + ${moduleid} + id + = + true + true + + + + ${host} + ${port} + ${protocol} + + /mod/quiz/view.php + GET + true + false + true + false + + Java + + + + + + false + quiz_opened + (This quiz opened at) + $1$ + + + true + + + + + The quiz will not be available until + + Assertion.response_data + false + 2 + + + + + + quizid + //*/form[@action="https://${host}/mod/quiz/startattempt.php"]//*/input[@name='cmid'][1]/@value + false + true + false + true + true + false + -1 + + + + 500 + 200 + + + + 1000 + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + false + true + false + false + false + true + 0 + true + true + true + true + true + + + + + + + + + + + + false + ${moduleid} + cmid + = + true + true + + + false + ${sesskey} + = + true + sesskey + + + + ${host} + ${port} + ${protocol} + + /mod/quiz/startattempt.php + POST + true + false + true + false + + Java + + + + + + + What's your username + Is the LMS Performing acceptably? + What is 2 \+ 2\? + + Assertion.response_data + false + 2 + + + + + 2000 + 1000 + + + + 1000 + + + + + attemptid + //*/form[@action="https://${host}/mod/quiz/processattempt.php"]//*/input[@name='attempt'][1]/@value + false + true + false + true + true + false + -1 + + + + false + attemptid + \/mod\/quiz\/attempt\.php\?attempt=(\d+).*?">Continue<\/a> + $1$ + NULL + + children + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + false + true + false + false + false + true + 0 + true + true + true + true + true + + + + + + + + + + + false + 0 + = + true + q${attemptid}:1_:flagged + + + false + 1 + = + true + q${attemptid}:1_:sequencecheck + + + false + ${username}@${user_type} + = + true + q${attemptid}:1_answer + + + false + 0 + = + true + q${attemptid}:2_:flagged + + + false + 1 + = + true + q${attemptid}:2_:sequencecheck + + + false + ${__Random(0,1)} + = + true + q${attemptid}:2_answer + + + false + 0 + = + true + q${attemptid}:3_:flagged + + + false + 1 + = + true + q${attemptid}:3_:sequencecheck + + + false + ${__Random(3,5)} + = + true + q${attemptid}:3_answer + + + false + Next + = + true + next + + + false + ${attemptid} + = + true + attempt + + + false + 0 + = + true + thispage + + + false + -1 + = + true + nextpage + + + false + 0 + = + true + timeup + + + false + ${sesskey} + = + true + sesskey + + + false + + = + true + scrollpos + + + false + 1,2,3 + = + true + slots + + + + ${host} + ${port} + ${protocol} + utf-8 + /mod/quiz/processattempt.php + POST + true + false + true + true + + Java + + + + + + + Summary of attempt + + 2 + Assertion.response_data + false + + + + + 2000 + 1000 + + + + 1000 + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + false + true + false + false + false + true + 0 + true + true + true + true + true + + + + + + + + + + + false + ${sesskey} + = + true + sesskey + + + false + + = + true + slots + + + false + 0 + = + true + timeup + + + false + 1 + = + true + finishattempt + + + false + ${attemptid} + = + true + attempt + + + + ${host} + ${port} + ${protocol} + utf-8 + /mod/quiz/processattempt.php + POST + true + false + true + true + + Java + + + + + + + Finish review + + 2 + Assertion.response_data + false + + + + + 2000 + 1000 + + + + 1000 + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + false + true + false + false + false + true + 0 + true + true + true + true + true + + + + + + + + + + + false + ${moduleid} + = + true + id + + + + ${host} + ${port} + ${protocol} + + /mod/quiz/view.php + GET + true + false + true + true + + Java + + + + + + + Summary of your previous attempts + + 2 + Assertion.response_data + false + + + + + + Highest grade + + 2 + Assertion.response_data + false + + + + + 4000 + 3000 + + + + 1000 + + + + + false + true + false + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + true + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + false + false + false + false + false + false + 0 + true + true + + + + + + + 2000 + 1000 + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + false + true + false + false + false + true + 0 + true + true + true + true + true + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + false + true + false + false + false + true + 0 + true + true + true + true + true + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + false + true + false + false + false + true + 0 + true + true + true + true + true + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + false + true + false + false + false + true + 0 + true + true + true + true + true + + + + 1000 + false + + + + + false + false + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + false + true + false + false + false + true + 0 + true + true + true + true + true + + + + 1000 + false + + + + + false + false + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + false + true + false + false + false + true + 0 + true + true + true + true + true + + + + 500 + false + + + + + false + false + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + false + true + false + false + false + true + 0 + true + true + true + true + true + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + false + true + false + false + false + true + 0 + true + true + true + true + true + + + + + + + + + + + diff --git a/loadtest/time-gated-exam-test.jmx b/loadtest/time-gated-exam-test.jmx new file mode 100755 index 00000000..f7611091 --- /dev/null +++ b/loadtest/time-gated-exam-test.jmx @@ -0,0 +1,1434 @@ + + + + + false + + + + + false + + + + + + + + login_host + ${__P(host, lb-d5t45x.westeurope.cloudapp.azure.com)} + = + + + host + ${__P(host, lb-d5t45x.westeurope.cloudapp.azure.com)} + = + + + threads + ${__P(threads,1000)} + = + Default 10 (for a quick GUI run) + + + rampup + ${__P(rampup,1000)} + = + Default 120 - 1/15 runtime default rampup + + + runtime + ${__P(runtime,3600)} + = + Default 1800 = 30 minutes = 30*60 seconds + + + default_connect_timeout + 2000 + = + 1.2 seconds to a TCP ack + + + default_response_timeout + 240000 + = + 4 Minutes for the page to respond + + + throughput_rate + ${__P(throughput_rate, 50)} + = + Default 650 should approximate ~ 7k page views / 5 mins cacti sample + + + activity_course_id + ${__P(activity_course_id, 2)} + = + This must match the course ID of the course with performance activities + + + quiz_delay + ${__P(quiz_delay, 100)} + -1 means don't run quiz + = + + + quiz_users + ${__P(quiz_users, 1)} + Number of quiz users to simulate + = + + + groupselect_delay + ${__P(groupselect_delay,-1)} + = + + + groupselect_users + ${__P(groupselect_users, 0)} + = + + + protocol + https + = + + + port + 443 + = + + + moodle_user_pass + ${__P(moodle_user_pass,testUserP@$$w0rd)} + = + + + + + + startnextloop + + false + 1 + + ${threads} + ${rampup} + 1513564238000 + 1513564538000 + true + ${runtime} + 0 + + + + + true + default + + + + + + + + User-Agent + Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.103 Safari/537.36 + + + Accept + */* + + + Accept-Language + en-us + + + + + + + + true + + VIEWSTATE + jsessionid + RandomNumber + username + userRandomChoice + sesskey + testBigFileLocation + testBigFileName + discussionid + courseList + xx_outputFromSplit + + + + + + ${__Random(1,2000,ReuseRandomNumber)} + m_azuretestuser_${__threadNum} + ${RandomNumber} + ${sesskey} + + + + ${__V(course_list_${RandomNumber})} + ${__split(${courseList}, course_id,|)} + + + true + + + + + + + ${host} + ${port} + ${protocol} + + / + GET + true + false + true + false + + Java + ${default_connect_timeout} + ${default_response_timeout} + + + + 1000 + + + + 8000 + 4000 + + + + + + + + ${login_host} + ${port} + ${protocol} + + /login/index.php + GET + true + false + true + false + + Java + ${default_connect_timeout} + ${default_response_timeout} + + + + 1000 + + + + 4000 + 1000 + + + + logintoken + input[name=logintoken] + value + NULL + false + + + + + + + + + + true + ${username} + username + = + true + true + + + true + ${moodle_user_pass} + password + = + true + true + + + false + ${logintoken} + = + true + logintoken + + + + ${login_host} + ${port} + ${protocol} + + /login/index.php + POST + true + false + true + false + true + + Java + ${default_connect_timeout} + ${default_response_timeout} + + + + + You are logged in as + + 2 + Assertion.response_data + false + + + + + + sesskey + //*[@name='sesskey'][1]/@value + false + true + false + -1 + + + + 1000 + + + + 5000 + 0 + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + false + true + false + false + false + true + 0 + true + true + true + true + true + + + + + + + + true + 10 + + + + + + + + + false + ${activity_course_id} + id + = + true + true + + + + ${host} + ${port} + ${protocol} + + /course/view.php + GET + true + false + true + false + + Java + + + + + + + Performance Testing Regular Quiz + + 2 + Assertion.response_data + true + + + + + false + classtopics_html + (?s)(<ul class="topics".*?)<\/div>.*?<\/div>.*?<\/div>.*?<\/div>.*?<\/div>.*?<div id="region-pre" + $1$ + NULL + + + + + false + html_chunk_perfquiz + (.{0,250}Performance Testing Regular Quiz) + $1$ + NULL + + + + + false + moduleid + \/mod\/quiz\/view\.php\?id=(\d+) + $1$ + NULL + + variable + html_chunk_perfquiz + + + + NULL + moduleid + substring-after(//*[contains(text(),'Performance Testing Regular Quiz')]/../@href,'id=') + false + true + false + true + false + true + variable + classtopics_html + -1 + + + + + moduleid + substring-after(//*/a/*[contains(text(),'Performance Testing Regular Quiz')]/../@href,'=') + false + true + false + true + true + false + -1 + + + + 2000 + 1000 + + + + 1000 + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + false + true + false + false + false + true + 0 + true + true + true + true + true + + + + + + + + ${__javaScript("${quiz_opened}" != "This quiz opened at")} + + + + + + + false + ${moduleid} + id + = + true + true + + + + ${host} + ${port} + ${protocol} + + /mod/quiz/view.php + GET + true + false + true + false + + Java + + + + + + false + quiz_opened + (This quiz opened at) + $1$ + + + true + + + + + The quiz will not be available until + + Assertion.response_data + false + 2 + + + + + + quizid + //*/form[@action="https://${host}/mod/quiz/startattempt.php"]//*/input[@name='cmid'][1]/@value + false + true + false + true + true + false + -1 + + + + 500 + 200 + + + + 1000 + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + false + true + false + false + false + true + 0 + true + true + true + true + true + + + + + + + + + + + + false + ${moduleid} + cmid + = + true + true + + + false + ${sesskey} + = + true + sesskey + + + + ${host} + ${port} + ${protocol} + + /mod/quiz/startattempt.php + POST + true + false + true + false + + Java + + + + + + + What's your username + Is the LMS Performing acceptably? + What is 2 \+ 2\? + + Assertion.response_data + false + 2 + + + + + 2000 + 1000 + + + + 1000 + + + + + attemptid + //*/form[@action="https://${host}/mod/quiz/processattempt.php"]//*/input[@name='attempt'][1]/@value + false + true + false + true + true + false + -1 + + + + false + attemptid + \/mod\/quiz\/attempt\.php\?attempt=(\d+).*?">Continue<\/a> + $1$ + NULL + + children + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + false + true + false + false + false + true + 0 + true + true + true + true + true + + + + + + + + + + + false + 0 + = + true + q${attemptid}:1_:flagged + + + false + 1 + = + true + q${attemptid}:1_:sequencecheck + + + false + ${username}@${user_type} + = + true + q${attemptid}:1_answer + + + false + 0 + = + true + q${attemptid}:2_:flagged + + + false + 1 + = + true + q${attemptid}:2_:sequencecheck + + + false + ${__Random(0,1)} + = + true + q${attemptid}:2_answer + + + false + 0 + = + true + q${attemptid}:3_:flagged + + + false + 1 + = + true + q${attemptid}:3_:sequencecheck + + + false + ${__Random(3,5)} + = + true + q${attemptid}:3_answer + + + false + Next + = + true + next + + + false + ${attemptid} + = + true + attempt + + + false + 0 + = + true + thispage + + + false + -1 + = + true + nextpage + + + false + 0 + = + true + timeup + + + false + ${sesskey} + = + true + sesskey + + + false + + = + true + scrollpos + + + false + 1,2,3 + = + true + slots + + + + ${host} + ${port} + ${protocol} + utf-8 + /mod/quiz/processattempt.php + POST + true + false + true + true + + Java + + + + + + + Summary of attempt + + 2 + Assertion.response_data + false + + + + + 2000 + 1000 + + + + 1000 + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + false + true + false + false + false + true + 0 + true + true + true + true + true + + + + + + + + + + + false + ${sesskey} + = + true + sesskey + + + false + + = + true + slots + + + false + 0 + = + true + timeup + + + false + 1 + = + true + finishattempt + + + false + ${attemptid} + = + true + attempt + + + + ${host} + ${port} + ${protocol} + utf-8 + /mod/quiz/processattempt.php + POST + true + false + true + true + + Java + + + + + + + Finish review + + 2 + Assertion.response_data + false + + + + + 2000 + 1000 + + + + 1000 + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + false + true + false + false + false + true + 0 + true + true + true + true + true + + + + + + + + + + + false + ${moduleid} + = + true + id + + + + ${host} + ${port} + ${protocol} + + /mod/quiz/view.php + GET + true + false + true + true + + Java + + + + + + + Summary of your previous attempts + + 2 + Assertion.response_data + false + + + + + + Highest grade + + 2 + Assertion.response_data + false + + + + + 4000 + 3000 + + + + 1000 + + + + + false + true + false + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + true + false + false + false + false + false + 0 + true + true + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + false + false + false + false + false + false + 0 + true + true + + + + + + + 2000 + 1000 + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + false + true + false + false + false + true + 0 + true + true + true + true + true + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + false + true + false + false + false + true + 0 + true + true + true + true + true + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + false + true + false + false + false + true + 0 + true + true + true + true + true + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + false + true + false + false + false + true + 0 + true + true + true + true + true + + + + 1000 + false + + + + + false + false + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + false + true + false + false + false + true + 0 + true + true + true + true + true + + + + 1000 + false + + + + + false + false + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + false + true + false + false + false + true + 0 + true + true + true + true + true + + + + 500 + false + + + + + false + false + + + + + + + + + diff --git a/managedApplication/Cleanup.md b/managedApplication/Cleanup.md new file mode 100644 index 00000000..9febbffd --- /dev/null +++ b/managedApplication/Cleanup.md @@ -0,0 +1,50 @@ +# Cleaning up a Test Deployment + +If you worked through the documentation in this section you will have +created a nubmer of resources and at least one entry into your Service +Catalog. This document will explain how to remove them all. + +## Prerequisites + +We need to ensure the [variables](Environment.md) are set up correctly. + +## Azure Active Directory + +``` bash +MOODLE_MANAGED_APP_AD_ID=$(az ad group list --filter="displayName eq '$MOODLE_MANAGED_APP_OWNER_GROUP_NAME'" --query [0].objectId --output tsv) +az ad group delete --group $MOODLE_MANAGED_APP_AD_ID +``` + +## Remove the Service Catalog Entry + +``` bash +az managedapp definition delete --resource-group $MOODLE_SERVICE_CATALOG_RG_NAME --ids $MOODLE_MANAGED_APP_ID +``` + +### Service catalog resource group + +If you create a resource group solely for the managed application you +are now deleting you can safely remove its resource group: + +``` bash +az group delete --name $MOODLE_SERVICE_CATALOG_RG_NAME --yes +``` + +## Managed Application + +By deleting the managed application Azure will automatically delete +the managed application infrastructure resource group as well (this +was created as part of the managed application deployment). + +First we need the application ID. + +``` bash +MOODLE_DEPLOYMENT_ID=$(az managedapp show --resource-group $MOODLE_DEPLOYMENT_RG_NAME --name $MOODLE_DEPLOYMENT_NAME) +``` + +Now we have the ID we can delete the application. + +``` bash +az managedapp delete --resource-group $MOODLE_DEPLOYMENT_RG_NAME --ids $MOODLE_DEPLOYMENT_ID +``` + diff --git a/managedApplication/DeployMoodleManagedApp.md b/managedApplication/DeployMoodleManagedApp.md new file mode 100644 index 00000000..aa095ac4 --- /dev/null +++ b/managedApplication/DeployMoodleManagedApp.md @@ -0,0 +1,108 @@ +# Deploy a Moodle Based Managed Application into a Customer's Subscription + +In this tutorial we'll demonstrate how your customers will deploy an +instance of your Moodle Based Managed Application in their +subscription. + +## Prerequisites + +In order for the following steps to work you must first have +[published a Moodle Based Managed Application](PublishMoodleManagedApplication.md) +into your service catalog. + +## Consume the Managed Application + +Once the Moodle on Azure Managed Application is published to your +service catalog you can now depoloy it from within the portal or using +the CLI. In the following commands we'll see how to do this in the CLI. + +### Setup a Resource Group for the Application + +First we need to get the id of the application. This was returned in +the output of the command to create the service catalog entry. +However, we'll use the CLI to retrieve it and record it into a +variable: + +``` bash +MOODLE_MANAGED_APP_ID=$(az managedapp definition show --name $MOODLE_MANAGED_APP_NAME --resource-group $MOODLE_SERVICE_CATALOG_RG_NAME --query id --output tsv) +``` + +Create the application resource group, this is the group in which the +customer will see the managed application. + +``` bash +az group create --name $MOODLE_DEPLOYMENT_RG_NAME --location=$MOODLE_DEPLOYMENT_LOCATION +``` + +Results: + +``` json +{ + "id": "/subscriptions/325e7c34-99fb-4190-aa87-1df746c67705/resourceGroups/MoodleManagedApp", + "location": "southcentralus", + "managedBy": null, + "name": "MoodleManagedApp", + "properties": { + "provisioningState": "Succeeded" + }, + "tags": null +} +``` + +### Customer Deployment + +When a customer wants to deploy an application they can do so using +either the Portal or the CLI. In this section we'll look at how this +is done in the CLI. + +#### Providing Parameters + +If we were using the portal our `CreateUIDefinition.json` file would +be used to create a user interface to define the parameters needed in +`mainTemplate.json`. When using the CLI we need to provide parameter +values for any parameters that don't have a default. To make it easier +to manage we'll put these parameter values into environment variables. + +For convenience our `mainTemplate.json` file has defaults for all +values. This means that there is no need to provide parameters in the +commandline, though you can override the defaults if you want to by +adding the `--parameters` attribute. This attribute can take either +a JSON string or a filename (preceded with an '@', e.g. '--parameters @parameters.json`) containing a JSON +definition for the paramters, e.g. + + { + "parameterName": { + "value": "some value" + }, + "anotherParameterName": { + "value": "another value" + } + } + +The Moodle template provides sensible defaults for almost every +parameter, the one exception to this is the SSH Public Key, used to +provide secure access to the VMs. For this example we will use the +defaults for all parameters, but we still need to create a parameters +file. A template file is provided here (see +`parameters-template.json`). The following command will replace the +placeholder in the parameters template file with an SSH key used for +testing puporses (this is created as part of the envrionment setup in +the prerequisites): + +``` bash +ssh_pub_key=`cat $MOODLE_SSH_KEY_FILENAME.pub` +echo $ssh_pub_key +sed "s|GEN-SSH-PUB-KEY|$ssh_pub_key|g" parameters-template.json > $MOODLE_MANAGED_APP_WORKSPACE/$MOODLE_DEPLOYMENT_NAME/parameters.json +``` + +If you want to have more control over the deployment configuration +simply add parameters to the template file and use that to create +parameter files for specific deployments. + +### Deploying the application + +Deploy the managed application and corresponding infrastructure. + +``` bash +az 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 +``` diff --git a/managedApplication/Environment.md b/managedApplication/Environment.md new file mode 100644 index 00000000..096255d8 --- /dev/null +++ b/managedApplication/Environment.md @@ -0,0 +1,65 @@ +# Setup Environment + +For convenience most of the configuration values we need to create and +manage our Moodle Managed Application we'll create a numer of +Environment Variables. In order to store any generated files and +configurations we will also create a workspace. + +NOTE: If you are running these scripts through SimDem you can +customize these values by copying and editing `env.json` into +`env.local.json`. + +## Setup for Publishing the Moodle Managed Application + +``` bash +MOODLE_MANAGED_APP_OWNER_GROUP_NAME=MoodleOwner +MOODLE_MANAGED_APP_OWNER_NICKNAME=MoodleOwner +MOODLE_SERVICE_CATALOG_LOCATION=southcentralus +MOODLE_SERVICE_CATALOG_RG_NAME=MoodleManagedAppServiceCatalogRG +MOODLE_MANAGED_APP_NAME=MoodleManagedApp +MOODLE_MANAGED_APP_LOCK_LEVEL=ReadOnly +MOODLE_MANAGED_APP_DISPLAY_NAME=Moodle +MOODLE_MANAGED_APP_DESCRIPTION="Moodle on Azure as a Managed Application" +``` + +## Setup for Consuming the Moodle Managed Application + +Create an id for the resource group that will be managed by the +managed application provider. This is the resource group that +infrastructure will be deployed into. The end user does not, +generally, manage this group. + +``` bash +SUBSCRIPTION_ID=$(az account show --query id --output tsv) +MOODLE_MANAGED_RG_ID=/subscriptions/$SUBSCRIPTION_ID/resourceGroups/MoodleInfrastructure +``` + +We'll also need a resource group for the application deployment. This is the +resource group into which the application is deployed. This is the resource group that +the provider of the managed application will have access to. + +``` bash +MOODLE_DEPLOYMENT_RG_NAME=MoodleManagedAppRG +MOODLE_DEPLOYMENT_LOCATION=southcentralus +MOODLE_DEPLOYMENT_NAME=MoodleManagedApp +``` + +## Workspace + +We need a workspace for storing configuration files and other +per-deployment artifacts: + +``` shell +MOODLE_MANAGED_APP_WORKSPACE=~/.moodle +mkdir -p $MOODLE_MANAGED_APP_WORKSPACE/$MOODLE_DEPLOYMENT_NAME +``` + +## SSH Key + +We use SSH for secure communication with our hosts. The following line +will check there is a valid SSH key available and, if not, create one. + +``` +MOODLE_SSH_KEY_FILENAME=~/.ssh/moodle_managedapp_id_rsa +if [ ! -f "$MOODLE_SSH_KEY_FILENAME" ]; then ssh-keygen -t rsa -N "" -f $MOODLE_SSH_KEY_FILENAME; fi +``` diff --git a/managedApplication/PublishMoodleManagedApplication.md b/managedApplication/PublishMoodleManagedApplication.md new file mode 100644 index 00000000..0585da31 --- /dev/null +++ b/managedApplication/PublishMoodleManagedApplication.md @@ -0,0 +1,166 @@ +# Publish a Moodle Based Managed Appliction to Service Catalog + +In this document we will look at how to publish a Moodle based Managed +Application into your Service Catalog so that you can allow your +customers to deploy the application into their subscriptions. If you +are not sure why you would do this you might want to read our [Moodle +Based Managed Application Introduction](README.md) first. + +## Prerequisites + +In the following sections we demonstrate how to use the Azure CLI to +work with a Moodle based Managed Application. For convenience these +commands use a variety of [environment variables](Environment.md) that +should be configured first. + +## Defining the Resources (mainTemplate.json) + +The `mainTemplate.json` file defines the Azure resources that are +provisioned as part of the managed application. We've already done the +majority of the work here for you (see `azuredeploy.json` in the root +of this repository). The `mainTemplate.json` file is where you +customize the configuration and, optionally, add additional resources. + +For the purposes of our demo we will use the ARM template from the +root of our project as the main tamplate. + +This file is a regular [Azure Resource Manager template](https://docs.microsoft.com/en-us/azure/azure-resource-manager/resource-group-overview). + +## User Interface Definition (createUIDefinition.json) + +The `createUIDefinition.json` file describes the user interface needed +to configure the managed application. It defines how the user provides +input for each of the parameters (specified in `mainTemplate.json`). + +An initial `createUIDefinition.json` file is provided in +`managedApplication/creatueUIDefinition.json`. This files is +sufficient to get you started building your own Moodle based Managed +Applications. + +See [Create UI Definition +documentation](https://docs.microsoft.com/en-us/azure/managed-applications/create-uidefinition-overview) for more information. + +## Create an Azure Active Directory User Group or Application + +You will need to create one ore more user group or appliction in Azure +Active Directory to allow you to manage the applications resources on +behalf of your customer. These groups or application can be given any +built-in Role-Based Access Control (RBAC) role, such as 'Owner' or +'Contributor'. By creating more than one such group or application you +can configure access to your customers resources based on the specific +needs of each role in your organization. + +Azure has full documentation on [creating a group in Azure Active +Directory](https://docs.microsoft.com/en-us/azure/active-directory/active-directory-groups-create-azure-portal). The commands below will create a single 'owner' role for +use in the examples below. + +If the Group already exists we don't want to create a new one, so we +will try to get the Group ID first: + +``` bash +MOODLE_MANAGED_APP_AD_ID=$(az ad group list --filter="displayName eq '$MOODLE_MANAGED_APP_OWNER_GROUP_NAME'" --query [0].objectId --output tsv) +``` + +At 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: + +``` bash +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 +``` + +Let's ensure that we have the object ID even if we created a new one. + +``` bash +MOODLE_MANAGED_APP_AD_ID=$(az ad group list --filter="displayName eq '$MOODLE_MANAGED_APP_OWNER_GROUP_NAME'" --query [0].objectId --output tsv) +``` + +You will also need the Role ID for your chosen role, here we will use +the built-in 'Owner' role: + +``` bash +MOODLE_MANAGED_APP_ROLE_ID=$(az role definition list --name Owner --query [].name --output tsv) +``` + +The Azure documentation has more information on how to work with [Azure Active Directory](https://docs.microsoft.com/en-us/azure/active-directory/manage-access-to-azure-resources). + +## Create a Resource Group for the Managed Application Service Catalog Entry + +``` bash +az group create --name $MOODLE_SERVICE_CATALOG_RG_NAME --location $MOODLE_SERVICE_CATALOG_LOCATION +``` + +## Publish to your Service Catalog using Azure CLI + +You can publish a Managed Application definition into your Service Catalog using +the Azure CLI. For convenience we'll set a few environment variables +to make it easier to work with the application. We'll need to construct +the authorization configuration from the app and role IDs retrieved +earlier. + +``` bash +MOODLE_MANAGED_APP_AUTHORIZATIONS=$MOODLE_MANAGED_APP_AD_ID:$MOODLE_MANAGED_APP_ROLE_ID +``` + +The following command will add your managed application definition to the Service Catalog. + +``` bash +az 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 +``` + +Results: + +``` json +{ + "artifacts": [ + { + "name": "ApplicationResourceTemplate", + "type": "Template", + "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" + }, + { + "name": "CreateUiDefinition", + "type": "Custom", + "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" + } + ], + "authorizations": [ + { + "principalId": "fdc3f6fb-cc24-4182-9943-b63e0ed67285", + "roleDefinitionId": "8e3af657-a8ff-443c-a75c-2fe8c4bcb635" + } + ], + "createUiDefinition": null, + "description": "Moodle on Azure as a Managed Application", + "displayName": "Moodle", + "id": "/subscriptions/325e7c34-99fb-4190-aa87-1df746c67705/resourceGroups/MoodleManagedAppServiceCatalogRG/providers/Microsoft.Solutions/applicationDefinitions/MoodleManagedApp", + "identity": null, + "isEnabled": "True", + "location": "southcentralus", + "lockLevel": "ReadOnly", + "mainTemplate": null, + "managedBy": null, + "name": "MoodleManagedApp", + "packageFileUri": null, + "resourceGroup": "MoodleManagedAppServiceCatalogRG", + "sku": null, + "tags": null, + "type": "Microsoft.Solutions/applicationDefinitions" +} +``` + +### [OPTIONAL] Package the files + +The `mainTemplate.json` and `createUIDefinition.json` files can be +packaged together in a zip file. Both files should be at the root level +of the zip. Once created the package needs to be uploaded to a location accessible +to Azure. We've published the samples to GitHub so you can experiment +with minimal effort. + +To use a package file remove the `--create-ui-definition` and +`--main-tamplate` arguments from the above CLI command instead provide +a URI for the package using `--package-file-uri` argument. + +## Next Steps + +Now that you have published a Moodle based Managed Application on Azure you can: + + 1. [Deploy Moodle into Customer Subscription](DeployMoodleManagedApp.md) diff --git a/managedApplication/README.md b/managedApplication/README.md new file mode 100644 index 00000000..18a2161d --- /dev/null +++ b/managedApplication/README.md @@ -0,0 +1,33 @@ +# Azure Managed Application + +[Azure Managed +Applications](https://docs.microsoft.com/en-us/azure/managed-applications/overview) enable you to offer your Moodle based +solutions to customers via the [Azure Marketplace](https://azuremarketplace.microsoft.com/en-us/marketplace/) or a Service Catalog. You define the +infrastructure for the solution, using the ARM templates in this +repository as a starting point, along with the terms for ongoing +management of the solution. The billing for your solution is handled +through Azure billing. + +## Why the Azure Marketplace and Azure Managed Applications for Moodle Hosting Providers +The 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. + +Until 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. + +With 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. + +## Why Moodle Managed Applications for IT Teams? +For IT teams, managed applications enable you to offer pre-approved configuration of Moodle +to 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. + +Read more about [Managed +Applications](https://docs.microsoft.com/en-us/azure/managed-applications/overview), +or keep reading here to see how to quickly get started providing your +own Moodle based services as Managed Applications. + +## Next Steps + + 1. [Publish a Managed Application Definition](PublishMoodleManagedApplication.md) + 2. [Deploy a Moodle Based Managed Application](DeployMoodleManagedApp.md) + 3. [Learn about submitting your application to the Azure Marketplace](https://docs.microsoft.com/en-us/azure/marketplace/marketplace-publishers-guide) + 4. [Submit your application to the Azure Marketplace](https://azuremarketplace.microsoft.com/en-us/sell/nominate) + diff --git a/managedApplication/createUIDefinition.json b/managedApplication/createUIDefinition.json new file mode 100644 index 00000000..e15b3e6c --- /dev/null +++ b/managedApplication/createUIDefinition.json @@ -0,0 +1,39 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/0.1.2-preview/CreateUIDefinition.MultiVm.json#", + "handler": "Microsoft.Compute.MultiVm", + "version": "0.1.2-preview", + "parameters": { + "basics": [ + {} + ], + "steps": [ + { + "name": "credentialsConfig", + "label": "VM Credentials", + "subLabel": { + "preValidation": "Provide credentials for accessing the VMs in your application.", + "postValidation": "Credentials configured." + }, + "bladeTitle": "Credentials", + "elements": [ + { + "name": "sshPublicKey", + "type": "Microsoft.Common.TextBox", + "label": "SSH Public Key", + "toolTip": "Public SSH Key to use for access", + "constraints": { + "required": true + } + } + ] + } + ], + "outputs": { + "sshPublicKey": "[steps('credentialsConfig').sshPublicKey]" + } + } +} + + + + diff --git a/managedApplication/parameters-template.json b/managedApplication/parameters-template.json new file mode 100644 index 00000000..e967dc5e --- /dev/null +++ b/managedApplication/parameters-template.json @@ -0,0 +1,8 @@ +{ + "sshPublicKey": { "value": "GEN-SSH-PUB-KEY" }, + "redisDeploySwitch": { "value": false }, + "dbServerType": { "value": "mysql" }, + "fileServerType": { "value": "nfs" }, + "autoscaleVmSku": { "value": "Standard_DS1_v2" }, + "fileServerDiskCount": { "value": 2 } +} diff --git a/metadata.json b/metadata.json index dc707885..cc26c631 100644 --- a/metadata.json +++ b/metadata.json @@ -1,7 +1,7 @@ { - "itemDisplayName": "Moodle autoscale with db/redis", - "description": "Deploys an autoscaling Moodle cluster with objectfs storage, Azure redis, Azure MySQL/Postgres, 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.", + "itemDisplayName": "Autoscalable Moodle on Azure", + "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.", "summary": "Moodle autoscale with redis/db/elasticsearch", - "githubUsername": "CatalystAUInf", - "dateUpdated": "2017-12-28" + "githubUsername": "hosungsmsft", + "dateUpdated": "2018-04-20" } diff --git a/nested/appgw.json b/nested/appgw.json new file mode 100644 index 00000000..b87e06b4 --- /dev/null +++ b/nested/appgw.json @@ -0,0 +1,173 @@ +{ + "$schema": "http://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json", + "contentVersion": "1.0.0.0", + "parameters": { + "moodleCommon": { + "metadata": { + "description": "Common Moodle values" + }, + "type": "object" + }, + "subnetIdAppGw": { + "metadata": { + "description": "Azure resource ID of the subnet where this App Gw should be deployed" + }, + "type": "string" + }, + "sslCertData": { + "metadata": { + "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." + }, + "type": "securestring" + } + }, + "resources": [ + { + "type": "Microsoft.Network/applicationGateways", + "name": "[parameters('moodleCommon').appGwName]", + "location": "[parameters('moodleCommon').location]", + "apiVersion": "2019-11-01", + "properties": { + "sku": { + "name": "[parameters('moodleCommon').appGwSkuName]", + "tier": "[parameters('moodleCommon').appGwSkuTier]", + "capacity": "[parameters('moodleCommon').appGwSkuCapacity]" + }, + "gatewayIPConfigurations": [ + { + "name": "appGwIpConfig", + "properties": { + "subnet": { + "id": "[parameters('subnetIdAppGw')]" + } + } + } + ], + "frontendIPConfigurations": [ + { + "name": "appGwFrontendIP", + "properties": { + "publicIPAddress": { + "id": "[variables('appGwPublicIPAddressID')]" + } + } + } + ], + "frontendPorts": [ + { + "name": "httpsFrontendPort", + "properties": { + "port": 443 + } + }, + { + "name": "httpFrontendPort", + "properties": { + "port": 80 + } + } + ], + "backendAddressPools": [ + { + "name": "[variables('appGwBePoolName')]" + } + ], + "backendHttpSettingsCollection": [ + { + "name": "appGwBackendHttpSettings", + "properties": { + "port": 80, + "protocol": "Http", + "cookieBasedAffinity": "Disabled" + } + } + ], + "sslCertificates": [ + { + "name": "appGatewaySslCert", + "properties": { + "data": "[parameters('sslCertData')]" + } + } + ], + "httpListeners": [ + { + "name": "appGwHttpsListener", + "properties": { + "frontendIPConfiguration": { + "Id": "[concat(variables('appGwID'), '/frontendIPConfigurations/appGwFrontendIP')]" + }, + "frontendPort": { + "Id": "[concat(variables('appGwID'), '/frontendPorts/httpsFrontendPort')]" + }, + "protocol": "Https", + "sslCertificate": { + "Id": "[concat(variables('appGwID'), '/sslCertificates/appGatewaySslCert')]" + } + } + }, + { + "name": "appGwHttpListener", + "properties": { + "frontendIPConfiguration": { + "Id": "[concat(variables('appGwID'), '/frontendIPConfigurations/appGwFrontendIP')]" + }, + "frontendPort": { + "Id": "[concat(variables('appGwID'), '/frontendPorts/httpFrontendPort')]" + }, + "protocol": "Http" + } + } + ], + "redirectConfigurations": [ + { + "name": "httpToHttps", + "properties": { + "redirectType":"Permanent", + "includePath" : true, + "includeQueryString" : true, + "targetListener": { + "id": "[concat(variables('appGwID'), '/httpListeners/appGwHttpsListener')]" + } + } + } + ], + "requestRoutingRules": [ + { + "Name": "httpsRule", + "properties": { + "ruleType": "Basic", + "httpListener": { + "id": "[concat(variables('appGwID'), '/httpListeners/appGwHttpsListener')]" + }, + "backendAddressPool": { + "id": "[concat(variables('appGwID'), '/backendAddressPools/', variables('appGwBePoolName'))]" + }, + "backendHttpSettings": { + "id": "[concat(variables('appGwID'), '/backendHttpSettingsCollection/appGwBackendHttpSettings')]" + } + } + }, + { + "Name": "httpRedirectRule", + "properties": { + "ruleType": "Basic", + "httpListener": { + "id": "[concat(variables('appGwID'), '/httpListeners/appGwHttpListener')]" + }, + "redirectConfiguration": { + "id": "[concat(variables('appGwID'), '/redirectConfigurations/httpToHttps')]" + } + } + } + ] + } + } + ], + "variables": { + "documentation1": "This sub-template creates an Azure Application Gateway for SSL offloading. It expects certain values in the 'common' datastructure.", + "appGwBePoolName": "[parameters('moodleCommon').appGwBePoolName]", + "appGwPublicIPAddressID": "[resourceId('Microsoft.Network/publicIPAddresses',parameters('moodleCommon').appGwPipName)]", + "appGwID": "[resourceId('Microsoft.Network/applicationGateways', parameters('moodleCommon').appGwName)]" + } +} \ No newline at end of file diff --git a/nested/blobStorageAccount.json b/nested/blobStorageAccount.json deleted file mode 100644 index d5d818a0..00000000 --- a/nested/blobStorageAccount.json +++ /dev/null @@ -1,58 +0,0 @@ -{ - "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "parameters": { - "moodleCommon": { - "metadata": { - "description": "Common Moodle values" - }, - "type": "object" - } - }, - "resources": [ - { - "kind": "BlobStorage", - "location": "[resourceGroup().location]", - "name": "[parameters('moodleCommon').blobStorageAccountName]", - "apiVersion": "2017-06-01", - "sku": { - "name": "[parameters('moodleCommon').blobStorageAccountType]" - }, - "properties": { - "accessTier": "Hot", - "encryption": { - "keySource": "Microsoft.Storage", - "services": { - "blob": { - "enabled": true - }, - "file": { - "enabled": true - } - } - }, - "networkAcls": { - "bypass": "AzureServices", - "defaultAction": "Allow", - "ipRules": [], - "virtualNetworkRules": [] - }, - "supportsHttpsTrafficOnly": true - }, - "type": "Microsoft.Storage/storageAccounts" - } - ], - "variables": { - "documentation1": "This sub-template creates a blob storage account. It expects certain values in the 'common' datastructure.", - "documentation2": " blobStorageAccountName - name of storage account", - "documentation3": " blobStorageAccountType - type of storage account", - "scriptUri": "[concat(parameters('moodleCommon').ScriptLocation,parameters('moodleCommon').moodleInstallScriptFilename)]", - "blobStorageAccountId": "[concat(resourceGroup().id,'/providers/Microsoft.Storage/storageAccounts/', parameters('moodleCommon').blobStorageAccountName)]" - }, - "outputs": { - "blobStorageAccountKey": { - "value": "[listKeys(variables('blobStorageAccountId'), '2017-06-01').keys[0].value]", - "type": "string" - } - } -} diff --git a/nested/controller.json b/nested/controller.json index 002a7978..cfaccc86 100644 --- a/nested/controller.json +++ b/nested/controller.json @@ -8,39 +8,31 @@ }, "type": "object" }, - "blobStorageAccountKey": { + "subnetIdWeb": { "metadata": { - "description": "Key used to create a storage container" + "description": "Azure resource ID of the subnet where this VM is to be deployed" }, "type": "string" }, - "redisPrimaryKey": { + "ctlrPubIpId": { "metadata": { - "description": "Key used for Redis authentication" + "description": "Resource ID of the controller VM public IP address" }, "type": "string" + }, + "vmSetupParamsObj": { + "metadata": { + "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)" + }, + "type": "object" } }, "resources": [ { - "apiVersion": "2015-06-15", - "location": "[resourceGroup().location]", - "name": "[parameters('moodleCommon').jboxPipName]", - "properties": { - "dnsSettings": { - "domainNameLabel": "[parameters('moodleCommon').jboxPipName]" - }, - "publicIPAllocationMethod": "Static" - }, - "tags": { - "displayName": "jboxpip" - }, - "type": "Microsoft.Network/publicIPAddresses" - }, - { - "apiVersion": "2016-03-30", - "location": "[resourceGroup().location]", - "name": "[parameters('moodleCommon').jboxNsgName]", + "type": "Microsoft.Network/networkSecurityGroups", + "apiVersion": "2017-10-01", + "location": "[parameters('moodleCommon').location]", + "name": "[parameters('moodleCommon').ctlrNsgName]", "properties": { "securityRules": [ { @@ -72,48 +64,49 @@ ] }, "tags": { - "displayName": "JumpBox NSG" - }, - "type": "Microsoft.Network/networkSecurityGroups" + "displayName": "Controller NSG" + } }, { - "apiVersion": "2015-06-15", + "type": "Microsoft.Network/networkInterfaces", + "apiVersion": "2017-10-01", "dependsOn": [ - "[concat('Microsoft.Network/publicIPAddresses/', parameters('moodleCommon').jboxPipName)]" + "[concat('Microsoft.Network/networkSecurityGroups/', parameters('moodleCommon').ctlrNsgName)]" ], - "location": "[resourceGroup().location]", - "name": "[parameters('moodleCommon').jboxNicName]", + "location": "[parameters('moodleCommon').location]", + "name": "[parameters('moodleCommon').ctlrNicName]", "properties": { + "networkSecurityGroup": { + "id": "[variables('nsgRef')]" + }, "ipConfigurations": [ { - "name": "ipcfgjbox", - "networkSecurityGroup": { - "id": "[variables('nsgRef')]" - }, + "name": "ipcfgctlr", "properties": { "privateIPAllocationMethod": "Dynamic", "publicIPAddress": { - "id": "[variables('pipRef')]" + "id": "[parameters('ctlrPubIpId')]" }, "subnet": { - "id": "[variables('subnetWebRef')]" + "id": "[parameters('subnetIdWeb')]" } } } - ] + ], + "enableAcceleratedNetworking": "[parameters('moodleCommon').enableAccelNwForCtlrVmSwitch]" }, "tags": { - "displayName": "jboxNic" - }, - "type": "Microsoft.Network/networkInterfaces" + "displayName": "ctlrNic" + } }, { - "apiVersion": "[parameters('moodleCommon').computeApi]", + "type": "Microsoft.Compute/virtualMachines", + "apiVersion": "2017-03-30", "dependsOn": [ - "[concat('Microsoft.Network/networkInterfaces/', parameters('moodleCommon').jboxNicName)]" + "[concat('Microsoft.Network/networkInterfaces/', parameters('moodleCommon').ctlrNicName)]" ], - "location": "[resourceGroup().location]", - "name": "[parameters('moodleCommon').jboxVmName]", + "location": "[parameters('moodleCommon').location]", + "name": "[parameters('moodleCommon').ctlrVmName]", "properties": { "hardwareProfile": { "vmSize": "[parameters('moodleCommon').controllerVmSku]" @@ -127,7 +120,9 @@ }, "osProfile": { "adminUsername": "[parameters('moodleCommon').sshUsername]", - "computerName": "[parameters('moodleCommon').jboxVmName]", + "computerName": "[parameters('moodleCommon').ctlrVmName]", + "secrets": "[parameters('moodleCommon').ctlrVmSecrets]", + "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')))]", "linuxConfiguration": { "disablePasswordAuthentication": true, "ssh": { @@ -141,54 +136,50 @@ } }, "storageProfile": { - "dataDisks": [], "imageReference": "[parameters('moodleCommon').osType]", "osDisk": { - "createOption": "fromImage", + "createOption": "FromImage", + "diskSizeGB": 256, "managedDisk": { - "storageAccountType": "Standard_LRS" + "storageAccountType": "[parameters('moodleCommon').osDiskStorageType]" }, - "name": "[parameters('moodleCommon').jboxVmName]" - } + "name": "[parameters('moodleCommon').ctlrVmName]" + }, + "dataDisks": "[take(variables('nfsDiskArray'),if(equals(parameters('moodleCommon').fileServerType,'nfs'), parameters('moodleCommon').fileServerDiskCount, 0))]" } }, "tags": { - "displayName": "Gluster Virtual Machine" - }, - "type": "Microsoft.Compute/virtualMachines" + "displayName": "Controller Virtual Machine" + } }, { - "apiVersion": "2015-01-01", + "condition": "[parameters('moodleCommon').applyScriptsSwitch]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2017-05-10", "dependsOn": [ - "[concat('Microsoft.Compute/virtualMachines/', parameters('moodleCommon').jboxVmName)]" + "[concat('Microsoft.Compute/virtualMachines/', parameters('moodleCommon').ctlrVmName)]" ], - "name": "[concat(parameters('moodleCommon').jboxVmName,'-ScriptProcessor')]", + "name": "[concat(parameters('moodleCommon').ctlrVmName,'-ScriptProcessor')]", "properties": { "mode": "Incremental", "parameters": { "moodleCommon": { "value": "[parameters('moodleCommon')]" - }, - "blobStorageAccountKey": { - "value": "[parameters('blobStorageAccountKey')]" - }, - "redisPrimaryKey": { - "value": "[parameters('redisPrimaryKey')]" } - }, "templateLink": { - "uri": "[concat( parameters('moodleCommon').baseTemplateUrl,'controllerconfig',parameters('moodleCommon').applyScriptsSwitch,'.json')]" + "uri": "[concat(parameters('moodleCommon').baseTemplateUrl, 'controllersetup.json', parameters('moodleCommon').artifactsSasToken)]" } - }, - "type": "Microsoft.Resources/deployments" + } }, { - "apiVersion": "2015-01-01", + "condition": "[parameters('moodleCommon').azureBackupSwitch]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2017-05-10", "dependsOn": [ - "[concat('Microsoft.Compute/virtualMachines/',parameters('moodleCommon').jboxVmName)]" + "[concat('Microsoft.Compute/virtualMachines/',parameters('moodleCommon').ctlrVmName)]" ], - "name": "[concat(parameters('moodleCommon').jboxVmName,'-Backup')]", + "name": "[concat(parameters('moodleCommon').ctlrVmName,'-Backup')]", "properties": { "mode": "Incremental", "parameters": { @@ -196,14 +187,13 @@ "value": "[parameters('moodleCommon')]" }, "vmName": { - "value": "[parameters('moodleCommon').jboxVmName]" + "value": "[parameters('moodleCommon').ctlrVmName]" } }, "templateLink": { - "uri": "[concat( parameters('moodleCommon').baseTemplateUrl,'recoveryservicesEnlist',parameters('moodleCommon').azureBackupSwitch,'.json')]" + "uri": "[concat(parameters('moodleCommon').baseTemplateUrl,'recoveryservicesEnlist.json',parameters('moodleCommon').artifactsSasToken)]" } - }, - "type": "Microsoft.Resources/deployments" + } } ], "variables": { @@ -211,21 +201,33 @@ "documentation02": "It expects certain values in the 'common' datastructure.", "documentation03": " vnetName - name of virtual network", "documentation04": " subnetWeb - name of subnet for controller (and vm scale set)", - "documentation05": " computeApi - the 'managed' resources need to all use this value", - "documentation06": " jboxPipName - name of Public IP address for the jumpbox (note that none of the other VM's get a PIP - just the jumpbox", - "documentation07": " jboxNicName - name of the network interface (all VM's must hae a nic) to crate, tied to the public IP address", - "documentation08": " jboxNsgName - name of the network security group, regulating access to/from the jumpbox", + "documentation06": " ctlrPipName - name of Public IP address for the controller (note that none of the other VM's get a PIP - just the controller", + "documentation07": " ctlrNicName - name of the network interface (all VM's must hae a nic) to crate, tied to the public IP address", + "documentation08": " ctlrNsgName - name of the network security group, regulating access to/from the controller", "documentation09": "This sub-template calls other sub-templates", "documentation10": " controllerconfig - conditionally applies post-deployment script on the VM", "documentation18": " recoveryservicesEnlist - conditionally enlists the VM into the backup regimen", - "nicRef": "[resourceId('Microsoft.Network/networkInterfaces', parameters('moodleCommon').jboxNicName)]", - "nsgRef": "[resourceId('Microsoft.Network/networkSecurityGroups', parameters('moodleCommon').jboxNsgName)]", - "pipRef": "[resourceId('Microsoft.Network/publicIPAddresses', parameters('moodleCommon').jboxPipName)]", - "subnetWebRef": "[concat(resourceId('Microsoft.Network/virtualNetworks', parameters('moodleCommon').vnetName), '/subnets/',parameters('moodleCommon').subnetWeb)]" + "nicRef": "[resourceId('Microsoft.Network/networkInterfaces', parameters('moodleCommon').ctlrNicName)]", + "nsgRef": "[resourceId('Microsoft.Network/networkSecurityGroups', parameters('moodleCommon').ctlrNsgName)]", + "singleQuote": "'", + "copy": [ + { + "name": "nfsDiskArray", + "count": 8, + "input": { + "managedDisk": { + "storageAccountType": "Premium_LRS" + }, + "diskSizeGB": "[parameters('moodleCommon').fileServerDiskSize]", + "lun": "[copyIndex('nfsDiskArray')]", + "createOption": "Empty" + } + } + ] }, "outputs": { "controllerIP": { - "value": "[reference(resourceId('Microsoft.Network/publicIPAddresses', parameters('moodleCommon').jboxPipName), '2015-06-15').ipAddress]", + "value": "[reference(resourceId('Microsoft.Network/publicIPAddresses', parameters('moodleCommon').ctlrPipName), '2017-10-01').ipAddress]", "type": "string" } } diff --git a/nested/controllerconfig0.json b/nested/controllerconfig0.json deleted file mode 100644 index d660143e..00000000 --- a/nested/controllerconfig0.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "parameters": { - "moodleCommon": { - "metadata": { - "description": "Common Moodle values" - }, - "type": "object" - }, - "blobStorageAccountKey": { - "metadata": { - "description": "Key used to create a storage container" - }, - "type": "string" - }, - "redisPrimaryKey": { - "metadata": { - "description": "Key used for Redis authentication" - }, - "type": "string" - } - }, - "resources": [], - "variables": { - "documentation1": "This sub-template intentionally blank - a blank template allows for an optional exectuion strategy", - "documentation2": "", - "documentation3": "You should look for a template of the same name, but with a 1 instead of a 0 (at the end)" - } -} diff --git a/nested/controllerconfig1.json b/nested/controllerconfig1.json deleted file mode 100644 index 37474298..00000000 --- a/nested/controllerconfig1.json +++ /dev/null @@ -1,63 +0,0 @@ -{ - "$schema": "http://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "parameters": { - "moodleCommon": { - "metadata": { - "description": "Common Moodle values" - }, - "type": "object" - }, - "blobStorageAccountKey": { - "metadata": { - "description": "Key used to create a storage container" - }, - "type": "string" - }, - "redisPrimaryKey": { - "metadata": { - "description": "Key used for Redis authentication" - }, - "type": "string" - } - }, - "resources": [ - { - "apiVersion": "2015-06-15", - "location": "[resourceGroup().location]", - "name": "[concat(parameters('moodleCommon').jboxVmName,'/','install_moodle')]", - "properties": { - "autoUpgradeMinorVersion": true, - "publisher": "Microsoft.OSTCExtensions", - "settings": { - "commandToExecute": "[variables('cmdExec')]", - "fileUris": [ - "[variables('scriptUri')]" - ] - }, - "type": "CustomScriptForLinux", - "typeHandlerVersion": "1.4" - }, - "tags": { - "displayName": "install_moodle" - }, - "type": "Microsoft.Compute/virtualMachines/extensions" - } - ], - "variables": { - "cmdExec": "[concat('sh ', parameters('moodleCommon').moodleInstallScriptFilename, ' ', parameters('moodleCommon').moodleVersion, ' ', concat(parameters('moodleCommon').gfsNameRoot, '0'), ' ', 'data', ' ', parameters('moodleCommon').siteURL, ' ', concat(parameters('moodleCommon').dbServerType, '-', parameters('moodleCommon').resourcesPrefix, '.', parameters('moodleCommon').dbServerType, '.database.azure.com'), ' ', parameters('moodleCommon').moodleDbName, ' ', parameters('moodleCommon').moodleDbUser, ' ', parameters('moodleCommon').moodleDbPass, ' ', parameters('moodleCommon').moodleAdminPass, ' ', concat(parameters('moodleCommon').dbLogin, '@', parameters('moodleCommon').dbServerType, '-', parameters('moodleCommon').resourcesPrefix), ' ', parameters('moodleCommon').dbLoginPassword, ' ', parameters('moodleCommon').blobStorageAccountName, ' ', parameters('blobStorageAccountKey'), ' ', parameters('moodleCommon').moodleDbUserAzure, ' ', parameters('moodleCommon').redisDns, ' ', parameters('redisPrimaryKey'), ' ', parameters('moodleCommon').elasticVm1IP)]", - "documentation01": "This sub-template applies a specific post-deployment script to the controller vm", - "documentation02": "It expects certain values in the 'common' datastructure.", - "documentation03": " ScriptLocation - web URI", - "documentation04": " moodleInstallScriptFilename - name of script file", - "documentation05": " siteURL - URL of the website", - "documentation06": " gfsNameRoot - nameroot of gluster farm - note that the code applies a 0 to get to the first node", - "documentation07": " jboxVmName - name of the controller/jumpb ox VM", - "documentation08": " dbServerType - postgres or mysql", - "documentation09": " moodleDbName - database name for moodle", - "documentation10": " moodleDbUser - database user for moodle", - "documentation11": " moodleDbPass - database password for moodleDbUser", - "documentation11": " moodleAdminPass - password for moodle admin user", - "scriptUri": "[concat(parameters('moodleCommon').ScriptLocation,parameters('moodleCommon').moodleInstallScriptFilename)]" - } -} diff --git a/nested/controllersetup.json b/nested/controllersetup.json new file mode 100644 index 00000000..a6f8f092 --- /dev/null +++ b/nested/controllersetup.json @@ -0,0 +1,57 @@ +{ + "$schema": "http://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "moodleCommon": { + "metadata": { + "description": "Common Moodle values" + }, + "type": "object" + } + }, + "resources": [ + { + "type": "Microsoft.Compute/virtualMachines/extensions", + "apiVersion": "2017-03-30", + "location": "[parameters('moodleCommon').location]", + "name": "[concat(parameters('moodleCommon').ctlrVmName,'/','install_moodle')]", + "properties": { + "autoUpgradeMinorVersion": true, + "publisher": "Microsoft.Azure.Extensions", + "settings": { + "fileUris": [ + "[variables('scriptUri')]", + "[parameters('moodleCommon').commonFunctionsScriptUri]" + ] + }, + "protectedSettings":{ + "commandToExecute": "[concat('bash ', parameters('moodleCommon').moodleInstallScriptFilename, ' ', parameters('moodleCommon').moodleOnAzureConfigsJsonPath)]" + }, + "type": "CustomScript", + "typeHandlerVersion": "2.0" + }, + "tags": { + "displayName": "install_moodle" + } + } + ], + "variables": { + "documentation01": "This sub-template applies a specific post-deployment script to the controller vm", + "documentation02": "It expects certain values in the 'common' datastructure.", + "documentation03": " scriptLocation - web URI", + "documentation04": " moodleInstallScriptFilename - name of script file", + "documentation05": " siteURL - URL of the website", + "documentation06": " gfsNameRoot - nameroot of gluster farm - note that the code applies a 0 to get to the first node", + "documentation07": " ctlrVmName - name of the controller/jumpb ox VM", + "documentation08": " dbServerType - postgres or mysql", + "documentation09": " moodleDbName - database name for moodle", + "documentation10": " moodleDbUser - database user for moodle", + "documentation11": " moodleDbPass - database password for moodleDbUser", + "documentation12": " moodleAdminPass - password for moodle admin user", + "documentation13": " mssqlDbServiceObjectiveName - MS SQL porformance tier.", + "documentation14": " mssqlDbEdition - MS SQL edition tier", + "documentation15": " mssqlDbSize - MS SQL database size", + + "scriptUri": "[concat(parameters('moodleCommon').scriptLocation,parameters('moodleCommon').moodleInstallScriptFilename,parameters('moodleCommon').artifactsSasToken)]" + } +} diff --git a/nested/db-mssql.json b/nested/db-mssql.json new file mode 100644 index 00000000..46030980 --- /dev/null +++ b/nested/db-mssql.json @@ -0,0 +1,80 @@ +{ + "$schema": "http://schema.management.azure.com/schemas/2015-01-01-preview/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "moodleCommon": { + "metadata": { + "description": "Common Moodle values" + }, + "type": "object" + }, + "lbPubIp": { + "metadata": { + "description": "Public IP address of the deployed load balancer" + }, + "type": "string" + }, + "ctlrPubIp": { + "metadata": { + "description": "Public IP address of the deployed controller VM" + }, + "type": "string" + } + }, + "resources": [ + { + "type": "Microsoft.Sql/servers", + "apiVersion": "2015-05-01-preview", + "kind": "", + "location": "[parameters('moodleCommon').location]", + "name": "[parameters('moodleCommon').serverName]", + "properties": { + "administratorLogin": "[parameters('moodleCommon').dbLogin]", + "administratorLoginPassword": "[parameters('moodleCommon').dbLoginPassword]", + "version": "[parameters('moodleCommon').mssqlVersion]" + }, + "resources": [ + { + "apiVersion": "2014-04-01", + "dependsOn": [ + "[concat('Microsoft.Sql/servers/', parameters('moodleCommon').serverName)]" + ], + "location": "[parameters('moodleCommon').location]", + "name": "mssql-firewall-allow-lb", + "properties": { + "startIpAddress": "[parameters('lbPubIp')]", + "endIpAddress": "[parameters('lbPubIp')]" + }, + "type": "firewallRules" + }, + { + "apiVersion": "2014-04-01", + "dependsOn": [ + "[concat('Microsoft.Sql/servers/', parameters('moodleCommon').serverName)]" + ], + "location": "[parameters('moodleCommon').location]", + "name": "mssql-firewall-allow-ctlr", + "properties": { + "startIpAddress": "[parameters('ctlrPubIp')]", + "endIpAddress": "[parameters('ctlrPubIp')]" + }, + "type": "firewallRules" + } + ] + } + ], + "outputs": { + "dbFQDN": { + "type": "string", + "value": "[reference(parameters('moodleCommon').serverName).fullyQualifiedDomainName]" + } + }, + "variables": { + "documentation1": "This sub-template creates a mssql server. It expects certain values in the 'common' datastructure.", + "documentation10": " serverName - Mssql server name", + "documentation11": " mssqlVersion - Mssql version", + "documentation2": " administratorLogin - Mssql admin username", + "documentation3": " administratorLoginPassword - Mssql admin password", + "documentation4": " location - Mssql server location" + } +} diff --git a/nested/db-mysql.json b/nested/db-mysql.json new file mode 100644 index 00000000..7a410498 --- /dev/null +++ b/nested/db-mysql.json @@ -0,0 +1,97 @@ +{ + "$schema": "http://schema.management.azure.com/schemas/2014-04-01-preview/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "moodleCommon": { + "metadata": { + "description": "Common Moodle values" + }, + "type": "object" + }, + "lbPubIp": { + "metadata": { + "description": "Public IP address of the deployed load balancer" + }, + "type": "string" + }, + "ctlrPubIp": { + "metadata": { + "description": "Public IP address of the deployed controller VM" + }, + "type": "string" + } + }, + "resources": [ + { + "type": "Microsoft.DBforMySQL/servers", + "apiVersion": "2017-12-01", + "kind": "", + "location": "[parameters('moodleCommon').location]", + "name": "[parameters('moodleCommon').serverName]", + "properties": { + "administratorLogin": "[parameters('moodleCommon').dbLogin]", + "administratorLoginPassword": "[parameters('moodleCommon').dbLoginPassword]", + "sslEnforcement": "[parameters('moodleCommon').sslEnforcement]", + "storageProfile": { + "storageMB": "[mul(parameters('moodleCommon').mysqlPgresStgSizeGB, 1024)]", + "backupRetentionDays": "35", + "geoRedundantBackup": "Enabled" + }, + "version": "[parameters('moodleCommon').mysqlVersion]" + }, + "sku": { + "capacity": "[parameters('moodleCommon').mysqlPgresVcores]", + "name": "[parameters('moodleCommon').mysqlPgresSkuName]", + "tier": "[parameters('moodleCommon').mysqlPgresSkuTier]", + "family": "[parameters('moodleCommon').mysqlPgresSkuHwFamily]" + }, + "resources": [ + { + "apiVersion": "2017-12-01", + "dependsOn": [ + "[concat('Microsoft.DBforMySQL/servers/', parameters('moodleCommon').serverName)]" + ], + "location": "[parameters('moodleCommon').location]", + "name": "mysql-firewall-allow-lb", + "properties": { + "startIpAddress": "[parameters('lbPubIp')]", + "endIpAddress": "[parameters('lbPubIp')]" + }, + "type": "firewallRules" + }, + { + "apiVersion": "2017-12-01", + "dependsOn": [ + "[concat('Microsoft.DBforMySQL/servers/', parameters('moodleCommon').serverName)]" + ], + "location": "[parameters('moodleCommon').location]", + "name": "mysql-firewall-allow-ctlr", + "properties": { + "startIpAddress": "[parameters('ctlrPubIp')]", + "endIpAddress": "[parameters('ctlrPubIp')]" + }, + "type": "firewallRules" + } + ] + } + ], + "outputs": { + "dbFQDN": { + "type": "string", + "value": "[reference(parameters('moodleCommon').serverName).fullyQualifiedDomainName]" + } + }, + "variables": { + "documentation1": "This sub-template creates a mysql server. It expects certain values in the 'common' datastructure.", + "documentation10": " serverName - Mysql server name", + "documentation11": " mysqlVersion - Mysql version", + "documentation2": " administratorLogin - mysql admin username", + "documentation3": " administratorLoginPassword - mysql admin password", + "documentation4": " location - Mysql server location", + "documentation5": " mysqlPgresVcores - Mysql database trasaction units", + "documentation7": " mysqlPgresSkuName - Mysql sku name", + "documentation8": " mysqlPgresStgSizeGB - Mysql sku size in mb", + "documentation9": " mysqlPgresSkuTier - Mysql sku tier", + "documentationA": " mysqlPgresSkuHwFamily - Mysql sku hardware family" + } +} diff --git a/nested/db-postgres.json b/nested/db-postgres.json new file mode 100644 index 00000000..3fc6404f --- /dev/null +++ b/nested/db-postgres.json @@ -0,0 +1,97 @@ +{ + "$schema": "http://schema.management.azure.com/schemas/2014-04-01-preview/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "moodleCommon": { + "metadata": { + "description": "Common Moodle values" + }, + "type": "object" + }, + "lbPubIp": { + "metadata": { + "description": "Public IP address of the deployed load balancer" + }, + "type": "string" + }, + "ctlrPubIp": { + "metadata": { + "description": "Public IP address of the deployed controller VM" + }, + "type": "string" + } + }, + "resources": [ + { + "type": "Microsoft.DBforPostgreSQL/servers", + "apiVersion": "2017-12-01", + "kind": "", + "location": "[parameters('moodleCommon').location]", + "name": "[parameters('moodleCommon').serverName]", + "properties": { + "administratorLogin": "[parameters('moodleCommon').dbLogin]", + "administratorLoginPassword": "[parameters('moodleCommon').dbLoginPassword]", + "sslEnforcement": "[parameters('moodleCommon').sslEnforcement]", + "storageProfile": { + "storageMB": "[mul(parameters('moodleCommon').mysqlPgresStgSizeGB, 1024)]", + "backupRetentionDays": "35", + "geoRedundantBackup": "Enabled" + }, + "version": "[parameters('moodleCommon').postgresVersion]" + }, + "sku": { + "capacity": "[parameters('moodleCommon').mysqlPgresVcores]", + "name": "[parameters('moodleCommon').mysqlPgresSkuName]", + "tier": "[parameters('moodleCommon').mysqlPgresSkuTier]", + "family": "[parameters('moodleCommon').mysqlPgresSkuHwFamily]" + }, + "resources": [ + { + "apiVersion": "2017-12-01", + "dependsOn": [ + "[concat('Microsoft.DBforPostgreSQL/servers/', parameters('moodleCommon').serverName)]" + ], + "location": "[parameters('moodleCommon').location]", + "name": "postgres-firewall-allow-lb", + "properties": { + "startIpAddress": "[parameters('lbPubIp')]", + "endIpAddress": "[parameters('lbPubIp')]" + }, + "type": "firewallRules" + }, + { + "apiVersion": "2017-12-01", + "dependsOn": [ + "[concat('Microsoft.DBforPostgreSQL/servers/', parameters('moodleCommon').serverName)]" + ], + "location": "[parameters('moodleCommon').location]", + "name": "postgres-firewall-allow-ctlr", + "properties": { + "startIpAddress": "[parameters('ctlrPubIp')]", + "endIpAddress": "[parameters('ctlrPubIp')]" + }, + "type": "firewallRules" + } + ] + } + ], + "outputs": { + "dbFQDN": { + "type": "string", + "value": "[reference(parameters('moodleCommon').serverName).fullyQualifiedDomainName]" + } + }, + "variables": { + "documentation1": "This sub-template creates a postgresql server. It expects certain values in the 'common' datastructure.", + "documentation10": " serverName - Postgresql server name", + "documentation11": " postgresVersion - Postgresql version", + "documentation2": " administratorLogin - postgresql admin username", + "documentation3": " administratorLoginPassword - postgresql admin password", + "documentation4": " location - Postgresql server location", + "documentation5": " mysqlPgresVcores - Postgresql database trasaction units", + "documentation7": " mysqlPgresSkuName - Postgresql sku name", + "documentation8": " mysqlPgresStgSizeGB - Postgresql sku size in mb", + "documentation9": " mysqlPgresSkuTier - Postgresql sku tier", + "documentationA": " mysqlPgresSkuHwFamily - Mysql sku hardware family" + } +} diff --git a/nested/diskSelection.json b/nested/diskSelection.json deleted file mode 100644 index dd16d8f0..00000000 --- a/nested/diskSelection.json +++ /dev/null @@ -1,92 +0,0 @@ -{ - "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "outputs": { - "dataDiskArray": { - "type": "array", - "value": "[take(variables('diskArray'),parameters('vmDiskCount'))]" - } - }, - "parameters": { - "vmDiskCount": { - "maxValue": 8, - "minValue": 2, - "type": "int" - }, - "vmName": { - "type": "string" - } - }, - "resources": [], - "variables": { - "diskArray": [ - { - "caching": "None", - "createOption": "Attach", - "lun": 0, - "managedDisk": { - "id": "[resourceId('Microsoft.Compute/disks', concat(parameters('vmName'),'-datadisk1'))]" - } - }, - { - "caching": "None", - "createOption": "Attach", - "lun": 1, - "managedDisk": { - "id": "[resourceId('Microsoft.Compute/disks', concat(parameters('vmName'),'-datadisk2'))]" - } - }, - { - "caching": "None", - "createOption": "Attach", - "lun": 2, - "managedDisk": { - "id": "[resourceId('Microsoft.Compute/disks', concat(parameters('vmName'),'-datadisk3'))]" - } - }, - { - "caching": "None", - "createOption": "Attach", - "lun": 3, - "managedDisk": { - "id": "[resourceId('Microsoft.Compute/disks', concat(parameters('vmName'),'-datadisk4'))]" - } - }, - { - "caching": "None", - "createOption": "Attach", - "lun": 4, - "managedDisk": { - "id": "[resourceId('Microsoft.Compute/disks', concat(parameters('vmName'),'-datadisk5'))]" - } - }, - { - "caching": "None", - "createOption": "Attach", - "lun": 5, - "managedDisk": { - "id": "[resourceId('Microsoft.Compute/disks', concat(parameters('vmName'),'-datadisk6'))]" - } - }, - { - "caching": "None", - "createOption": "Attach", - "lun": 6, - "managedDisk": { - "id": "[resourceId('Microsoft.Compute/disks', concat(parameters('vmName'),'-datadisk7'))]" - } - }, - { - "caching": "None", - "createOption": "Attach", - "lun": 7, - "managedDisk": { - "id": "[resourceId('Microsoft.Compute/disks', concat(parameters('vmName'),'-datadisk8'))]" - } - } - ], - "documentation01": "This sub-template provides a way to return a flexible number of drive configurations", - "documentation02": " vmName - used as apart of the naming of the disk(s)", - "documentation03": " vmDiskCount - used to limit the nubmer of configuraitons returned (via the TAKE function) " - } -} diff --git a/nested/elasticconfig0.json b/nested/elasticconfig0.json deleted file mode 100644 index eb1cf24a..00000000 --- a/nested/elasticconfig0.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "parameters": { - "moodleCommon": { - "metadata": { - "description": "Common Moodle values" - }, - "type": "object" - } - }, - "resources": [], - "variables": { - "documentation1": "This sub-template intentionally blank - a blank template allows for an optional exectuion strategy", - "documentation2": "", - "documentation3": "You should look for a template of the same name, but with a 1 instead of a 0 (at the end)" - } -} diff --git a/nested/gluster.json b/nested/gluster.json index ba1fabec..f7260a49 100644 --- a/nested/gluster.json +++ b/nested/gluster.json @@ -7,27 +7,36 @@ "description": "Common Moodle values" }, "type": "object" + }, + "subnetIdSan": { + "metadata": { + "description": "Azure resource ID of the subnet where this gluster cluster is to be deployed" + }, + "type": "string" } }, "resources": [ { - "apiVersion": "[parameters('moodleCommon').computeApi]", - "location": "[resourceGroup().location]", + "type": "Microsoft.Compute/availabilitySets", + "apiVersion": "2017-03-30", + "location": "[parameters('moodleCommon').location]", "name": "[parameters('moodleCommon').gfxAvailabilitySetName]", "properties": { - "managed": true, "platformFaultDomainCount": 2, "platformUpdateDomainCount": 5 }, + "sku": { + "name": "Aligned" + }, "tags": { "displayName": "Gluster Availability Set" - }, - "type": "Microsoft.Compute/availabilitySets" + } }, { - "apiVersion": "2015-01-01", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2017-05-10", "copy": { - "count": "[parameters('moodleCommon').glusterVmCount]", + "count": "[parameters('moodleCommon').fileServerVmCount]", "name": "vmloop" }, "dependsOn": [ @@ -42,21 +51,22 @@ }, "moodleCommon": { "value": "[parameters('moodleCommon')]" + }, + "subnetIdSan": { + "value": "[parameters('subnetIdSan')]" } }, "templateLink": { - "uri": "[concat( parameters('moodleCommon').baseTemplateUrl,'glustervm.json')]" + "uri": "[concat(parameters('moodleCommon').baseTemplateUrl,'glustervm.json',parameters('moodleCommon').artifactsSasToken)]" } - }, - "type": "Microsoft.Resources/deployments" + } } ], "variables": { "documentation1": "This sub-template drives the gluster (scale-out network-attached storage file system) creation process.", "documentation2": "It expects certain values in the 'common' datastructure.", - "documentation3": " computeApi - the 'managed' resources need to all use this value", "documentation4": " gfxAvailabilitySetName - name of availability set for the gluster farm", - "documentation5": " glusterVmCount - number of nodes to create", + "documentation5": " fileServerVmCount - number of nodes to create", "documentation6": "This sub-template calls other sub-templates", "documentation7": " glustervm - number of nodes in the gluster farm" } diff --git a/nested/glustervm.json b/nested/glustervm.json index 0b5684da..11a045be 100644 --- a/nested/glustervm.json +++ b/nested/glustervm.json @@ -13,12 +13,19 @@ "description": "Common Moodle values" }, "type": "object" + }, + "subnetIdSan": { + "metadata": { + "description": "Azure resource ID of the subnet where this gluster cluster is to be deployed" + }, + "type": "string" } }, "resources": [ { - "apiVersion": "2015-05-01-preview", - "location": "[resourceGroup().location]", + "type": "Microsoft.Network/networkInterfaces", + "apiVersion": "2017-10-01", + "location": "[parameters('moodleCommon').location]", "name": "[variables('nicName')]", "properties": { "ipConfigurations": [ @@ -27,73 +34,31 @@ "properties": { "privateIPAllocationMethod": "Dynamic", "subnet": { - "id": "[variables('subnetSanRef')]" + "id": "[parameters('subnetIdSan')]" } } } - ] + ], + "enableAcceleratedNetworking": "[parameters('moodleCommon').enableAccelNwForOtherVmsSwitch]" }, "tags": { "displayName": "Gluster VM NIC" - }, - "type": "Microsoft.Network/networkInterfaces" - }, - { - "apiVersion": "[parameters('moodleCommon').computeApi]", - "copy": { - "count": "[parameters('moodleCommon').glusterDiskCount]", - "name": "[variables('diskLoop')]" - }, - "location": "[resourceGroup().location]", - "name": "[concat(variables('vmName'),'-datadisk',copyIndex(1))]", - "properties": { - "accountType": "Premium_LRS", - "creationData": { - "createOption": "Empty" - }, - "diskSizeGB": "[parameters('moodleCommon').glusterDiskSize]" - }, - "tags": { - "displayName": "Gluster VM Data Disk" - }, - "type": "Microsoft.Compute/disks" - }, - { - "apiVersion": "2015-01-01", - "dependsOn": [ - "[variables('diskLoop')]" - ], - "name": "[concat(variables('vmName'),'-diskSelection')]", - "properties": { - "mode": "Incremental", - "parameters": { - "vmDiskCount": { - "value": "[parameters('moodleCommon').glusterDiskCount]" - }, - "vmName": { - "value": "[variables('vmName')]" - } - }, - "templateLink": { - "uri": "[concat(parameters('moodleCommon').baseTemplateUrl,'diskSelection.json')]" - } - }, - "type": "Microsoft.Resources/deployments" + } }, { - "apiVersion": "[parameters('moodleCommon').computeApi]", + "type": "Microsoft.Compute/virtualMachines", + "apiVersion": "2017-03-30", "dependsOn": [ - "[concat('Microsoft.Network/networkInterfaces/', variables('nicName'))]", - "[concat('Microsoft.Resources/deployments/',concat(variables('vmName'),'-diskSelection'))]" + "[concat('Microsoft.Network/networkInterfaces/', variables('nicName'))]" ], - "location": "[resourceGroup().location]", + "location": "[parameters('moodleCommon').location]", "name": "[variables('vmName')]", "properties": { "availabilitySet": { - "id": "[variables( 'asRef')]" + "id": "[variables('asRef')]" }, "hardwareProfile": { - "vmSize": "[parameters('moodleCommon').glusterVmSku]" + "vmSize": "[parameters('moodleCommon').fileServerVmSku]" }, "networkProfile": { "networkInterfaces": [ @@ -118,24 +83,38 @@ } }, "storageProfile": { - "dataDisks": "[reference(concat(variables('vmName'),'-diskSelection')).outputs.dataDiskArray.value]", "imageReference": "[parameters('moodleCommon').osType]", "osDisk": { - "createOption": "fromImage", + "createOption": "FromImage", "managedDisk": { - "storageAccountType": "Premium_LRS" + "storageAccountType": "[parameters('moodleCommon').osDiskStorageType]" }, "name": "[variables('vmName')]" - } + }, + "copy": [ + { + "name": "dataDisks", + "count": "[parameters('moodleCommon').fileServerDiskCount]", + "input": { + "managedDisk": { + "storageAccountType": "Premium_LRS" + }, + "diskSizeGB": "[parameters('moodleCommon').fileServerDiskSize]", + "lun": "[copyIndex('dataDisks')]", + "createOption": "Empty" + } + } + ] } }, "tags": { "displayName": "Gluster Virtual Machine" - }, - "type": "Microsoft.Compute/virtualMachines" + } }, { - "apiVersion": "2015-01-01", + "condition": "[parameters('moodleCommon').applyScriptsSwitch]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2017-05-10", "dependsOn": [ "[concat('Microsoft.Compute/virtualMachines/',variables('vmName'))]" ], @@ -154,13 +133,14 @@ } }, "templateLink": { - "uri": "[concat( parameters('moodleCommon').baseTemplateUrl,'glustervmconfig',parameters('moodleCommon').applyScriptsSwitch,'.json')]" + "uri": "[concat(parameters('moodleCommon').baseTemplateUrl,'glustervmsetup.json',parameters('moodleCommon').artifactsSasToken)]" } - }, - "type": "Microsoft.Resources/deployments" + } }, { - "apiVersion": "2015-01-01", + "condition": "[parameters('moodleCommon').azureBackupSwitch]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2017-05-10", "dependsOn": [ "[concat('Microsoft.Compute/virtualMachines/',variables('vmName'))]" ], @@ -176,35 +156,29 @@ } }, "templateLink": { - "uri": "[concat( parameters('moodleCommon').baseTemplateUrl,'recoveryservicesEnlist',parameters('moodleCommon').azureBackupSwitch,'.json')]" + "uri": "[concat(parameters('moodleCommon').baseTemplateUrl,'recoveryservicesEnlist.json',parameters('moodleCommon').artifactsSasToken)]" } - }, - "type": "Microsoft.Resources/deployments" + } } ], "variables": { - "asRef": "[ resourceId('Microsoft.Compute/availabilitySets', parameters('moodleCommon').gfxAvailabilitySetName)]", - "diskLoop": "[concat(variables('vmName'),'-disk')]", + "asRef": "[resourceId('Microsoft.Compute/availabilitySets', parameters('moodleCommon').gfxAvailabilitySetName)]", "documentation01": "This sub-template create the nodes of the gluster farm", "documentation02": "It expects certain values in the 'common' datastructure.", - "documentation03": " computeApi - the 'managed' resources need to all use this value", "documentation04": " gfxAvailabilitySetName - name of availability set for the gluster farm", "documentation05": " vnetName - name of virtual network", "documentation06": " subnetSan - name of subnet for gluster", "documentation07": " gfsNameRoot - nameroot for the gluster nodes - combined with counter to get actual name of each node - disk and nic follow the naming scheme", - "documentation08": " glusterVmSku - VM instance size for gluster nodes", + "documentation08": " fileServerVmSku - VM instance size for gluster nodes", "documentation09": " sshUsername - OS accountusername", - "documentation10": " sshPassword - OS account password", - "documentation11": " osType - an array of value that specifies the type of VM", + "documentation10": " osType - an array of value that specifies the type of VM", "documentation15": "This sub-template calls other sub-templates", - "documentation16": " diskSelection - returns an arry of disk names - for flexible sizing", "documentation17": " glustervmconfig - conditionally applies post-deployment script on the VM", "documentation18": " recoveryservicesEnlist - conditionally enlists the VM into the backup regimen", - "documentation19": " glusterDiskCount - Number of disks to raid0 for the gluster mount", - "documentation20": " glusterDiskSize - Size per disk for gluster", + "documentation19": " fileServerDiskCount - Number of disks to raid0 for the gluster mount", + "documentation20": " fileServerDiskSize - Size per disk for gluster", "nicName": "[concat(variables('vmName'),'-nic')]", "nicRef": "[resourceId('Microsoft.Network/networkInterfaces', variables('nicName'))]", - "subnetSanRef": "[concat(resourceId('Microsoft.Network/virtualNetworks', parameters('moodleCommon').vnetName), '/subnets/',parameters('moodleCommon').subnetSan)]", "vmName": "[concat(parameters('moodleCommon').gfsNameRoot,parameters('counter'))]" } } diff --git a/nested/glustervmconfig0.json b/nested/glustervmconfig0.json deleted file mode 100644 index 762e987e..00000000 --- a/nested/glustervmconfig0.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "parameters": { - "moodleCommon": { - "metadata": { - "description": "Common Moodle values" - }, - "type": "object" - }, - "vmName": { - "metadata": { - "description": "Name of VM to process script" - }, - "type": "string" - }, - "vmNumber": { - "metadata": { - "description": "Number of the VM in the pool" - }, - "type": "int" - } - }, - "resources": [], - "variables": { - "documentation1": "This sub-template intentionally blank - a blank template allows for an optional exectuion strategy", - "documentation2": "", - "documentation3": "You should look for a template of the same name, but with a 1 instead of a 0 (at the end)" - } -} diff --git a/nested/glustervmconfig1.json b/nested/glustervmsetup.json similarity index 72% rename from nested/glustervmconfig1.json rename to nested/glustervmsetup.json index 89b5c09d..0a9f25e1 100644 --- a/nested/glustervmconfig1.json +++ b/nested/glustervmsetup.json @@ -23,34 +23,36 @@ }, "resources": [ { - "apiVersion": "2015-05-01-preview", - "location": "[resourceGroup().location]", + "type": "Microsoft.Compute/virtualMachines/extensions", + "apiVersion": "2017-03-30", + "location": "[parameters('moodleCommon').location]", "name": "[concat(parameters('vmName'),'/','install_gluster')]", "properties": { - "publisher": "Microsoft.OSTCExtensions", + "publisher": "Microsoft.Azure.Extensions", "settings": { - "commandToExecute": "[variables('cmdExec')]", "fileUris": [ "[variables('scriptUri')]" ] }, - "type": "CustomScriptForLinux", - "typeHandlerVersion": "1.3" + "protectedSettings":{ + "commandToExecute": "[variables('cmdExec')]" + }, + "type": "CustomScript", + "typeHandlerVersion": "2.0" }, "tags": { "displayName": "GfsVmExtension" - }, - "type": "Microsoft.Compute/virtualMachines/extensions" + } } ], "variables": { - "cmdExec": "[concat('bash ', parameters('moodleCommon').glusterScriptFilename, ' ', parameters('moodleCommon').gfsNameRoot, ' ', parameters('moodleCommon').subnetSanPrefix, ' data ', parameters('vmNumber'), ' ', parameters('moodleCommon').glusterVmCount)]", + "cmdExec": "[concat('bash ', parameters('moodleCommon').glusterScriptFilename, ' ', parameters('moodleCommon').gfsNameRoot, ' ', parameters('moodleCommon').subnetSanPrefix, ' data ', parameters('vmNumber'), ' ', parameters('moodleCommon').fileServerVmCount)]", "documentation01": "This sub-template applies a specific post-deployment script to the gluster vms", "documentation02": "It expects certain values in the 'common' datastructure.", - "documentation03": " ScriptLocation - partial web URI (equivalent to folder)", + "documentation03": " scriptLocation - partial web URI (equivalent to folder)", "documentation04": " glusterScriptFilename - name of script file", "documentation06": " gfsNameRoot - nameroot of gluster farm - note that the code applies a vmNumber to get to the specific node", - "documentation07": " glusterVmCount - database (mariadb) password", - "scriptUri": "[concat(parameters('moodleCommon').ScriptLocation,parameters('moodleCommon').glusterScriptFilename)]" + "documentation07": " fileServerVmCount - number of gluster VMs", + "scriptUri": "[concat(parameters('moodleCommon').scriptLocation,parameters('moodleCommon').glusterScriptFilename,parameters('moodleCommon').artifactsSasToken)]" } } diff --git a/nested/mysql.json b/nested/mysql.json deleted file mode 100644 index 51b5198b..00000000 --- a/nested/mysql.json +++ /dev/null @@ -1,63 +0,0 @@ -{ - "$schema": "http://schema.management.azure.com/schemas/2014-04-01-preview/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "parameters": { - "moodleCommon": { - "metadata": { - "description": "Common Moodle values" - }, - "type": "object" - } - }, - "resources": [ - { - "apiVersion": "2017-04-30-preview", - "kind": "", - "location": "[resourceGroup().location]", - "name": "[parameters('moodleCommon').serverName]", - "properties": { - "administratorLogin": "[parameters('moodleCommon').dbLogin]", - "administratorLoginPassword": "[parameters('moodleCommon').dbLoginPassword]", - "sslEnforcement": "[parameters('moodleCommon').sslEnforcement]", - "storageMB": "[parameters('moodleCommon').skuSizeMB]", - "version": "[parameters('moodleCommon').mysqlVersion]" - }, - "resources": [ - { - "apiVersion": "2016-02-01-privatepreview", - "dependsOn": [ - "[concat('Microsoft.DBforMySQL/servers/', parameters('moodleCommon').serverName)]" - ], - "location": "[resourceGroup().location]", - "name": "[parameters('moodleCommon').firewallRuleName]", - "properties": { - "startIpAddress": "0.0.0.0", - "endIpAddress": "255.255.255.255" - }, - "type": "firewallrules" - } - ], - "sku": { - "capacity": "[parameters('moodleCommon').skuCapacityDTU]", - "family": "[parameters('moodleCommon').skuFamily]", - "name": "[parameters('moodleCommon').skuName]", - "size": "[parameters('moodleCommon').skuSizeMB]", - "tier": "[parameters('moodleCommon').skuTier]" - }, - "type": "Microsoft.DBforMySQL/servers" - } - ], - "variables": { - "documentation1": "This sub-template creates a mysql server. It expects certain values in the 'common' datastructure.", - "documentation10": " serverName - Mysql server name", - "documentation11": " mysqlVersion - Mysql version", - "documentation2": " administratorLogin - mysql admin username", - "documentation3": " administratorLoginPassword - mysql admin password", - "documentation4": " location - Mysql server location", - "documentation5": " skuCapacityDTU - Mysql database trasaction units", - "documentation6": " skuFamily - Mysql sku family", - "documentation7": " skuName - Mysql sku name", - "documentation8": " skuSizeMB - Mysql sku size in mb", - "documentation9": " skuTier - Mysql sku tier" - } -} diff --git a/nested/network-subnets.json b/nested/network-subnets.json new file mode 100644 index 00000000..4fa7ea34 --- /dev/null +++ b/nested/network-subnets.json @@ -0,0 +1,107 @@ +{ + "$schema": "http://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "moodleCommon": { + "metadata": { + "description": "Common Moodle values" + }, + "type": "object" + }, + "vnetName": { + "metadata": { + "description": "The name of the vnet where subnets should be created" + }, + "type": "string" + } + }, + "resources": [ + { + "type": "Microsoft.Network/virtualNetworks/subnets", + "apiVersion": "2017-10-01", + "name": "[concat(parameters('vnetName'), '/', parameters('moodleCommon').subnetWeb)]", + "location": "[parameters('moodleCommon').location]", + "properties": { + "addressPrefix": "[parameters('moodleCommon').subnetWebRange]" + } + }, + { + "type": "Microsoft.Network/virtualNetworks/subnets", + "apiVersion": "2017-10-01", + "name": "[concat(parameters('vnetName'), '/', parameters('moodleCommon').subnetSan)]", + "location": "[parameters('moodleCommon').location]", + "dependsOn": [ + "[resourceId('Microsoft.Network/virtualNetworks/subnets', parameters('vnetName'), parameters('moodleCommon').subnetWeb)]" + ], + "properties": { + "addressPrefix": "[parameters('moodleCommon').subnetSanRange]" + } + }, + { + "type": "Microsoft.Network/virtualNetworks/subnets", + "apiVersion": "2017-10-01", + "name": "[concat(parameters('vnetName'), '/', parameters('moodleCommon').subnetRedis)]", + "location": "[parameters('moodleCommon').location]", + "dependsOn": [ + "[resourceId('Microsoft.Network/virtualNetworks/subnets', parameters('vnetName'), parameters('moodleCommon').subnetSan)]" + ], + "properties": { + "addressPrefix": "[parameters('moodleCommon').subnetRedisRange]" + } + }, + { + "type": "Microsoft.Network/virtualNetworks/subnets", + "apiVersion": "2017-10-01", + "name": "[concat(parameters('vnetName'), '/', parameters('moodleCommon').subnetElastic)]", + "location": "[parameters('moodleCommon').location]", + "dependsOn": [ + "[resourceId('Microsoft.Network/virtualNetworks/subnets', parameters('vnetName'), parameters('moodleCommon').subnetRedis)]" + ], + "properties": { + "addressPrefix": "[parameters('moodleCommon').subnetElasticRange]" + } + }, + { + "type": "Microsoft.Network/virtualNetworks/subnets", + "apiVersion": "2017-10-01", + "name": "[concat(parameters('vnetName'), '/', parameters('moodleCommon').subnetTika)]", + "location": "[parameters('moodleCommon').location]", + "dependsOn": [ + "[resourceId('Microsoft.Network/virtualNetworks/subnets', parameters('vnetName'), parameters('moodleCommon').subnetElastic)]" + ], + "properties": { + "addressPrefix": "[parameters('moodleCommon').subnetTikaRange]" + } + }, + { + "condition": "[parameters('moodleCommon').vnetGwDeploySwitch]", + "type": "Microsoft.Network/virtualNetworks/subnets", + "apiVersion": "2017-10-01", + "name": "[concat(parameters('vnetName'), '/', parameters('moodleCommon').subnetGateway)]", + "location": "[parameters('moodleCommon').location]", + "dependsOn": [ + "[resourceId('Microsoft.Network/virtualNetworks/subnets', parameters('vnetName'), parameters('moodleCommon').subnetTika)]" + ], + "properties": { + "addressPrefix": "[parameters('moodleCommon').subnetGatewayRange]" + } + }, + { + "type": "Microsoft.Network/virtualNetworks/subnets", + "apiVersion": "2017-10-01", + "name": "[concat(parameters('vnetName'), '/', parameters('moodleCommon').subnetAppGw)]", + "location": "[parameters('moodleCommon').location]", + "dependsOn": [ + "[resourceId('Microsoft.Network/virtualNetworks/subnets', parameters('vnetName'), parameters('moodleCommon').subnetGateway)]" + ], + "properties": { + "addressPrefix": "[parameters('moodleCommon').subnetAppGwRange]" + } + } + ], + "variables": { + "documentation01": "This sub-template creates various subnets needed for various components of the cluster.", + "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.", + "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" + } +} diff --git a/nested/network-vnet-ddos.json b/nested/network-vnet-ddos.json new file mode 100644 index 00000000..f2dbdc4d --- /dev/null +++ b/nested/network-vnet-ddos.json @@ -0,0 +1,56 @@ +{ + "$schema": "http://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "moodleCommon": { + "metadata": { + "description": "Common Moodle values" + }, + "type": "object" + }, + "vnetName": { + "metadata": { + "description": "The name of the vnet to which the DDoS protection plan shoud be associated" + }, + "type": "string" + }, + "vNetAddressSpace": { + "metadata": { + "description": "The vNet Address Space to which the DDoS protection plan shoud be associated" + }, + "type": "string" + } + }, + "resources": [ + { + "type": "Microsoft.Network/ddosProtectionPlans", + "apiVersion": "2018-02-01", + "condition": "[parameters('moodleCommon').ddosSwitch]", + "location": "[parameters('moodleCommon').location]", + "name": "[parameters('moodleCommon').ddosPlanName]" + }, + { + "type": "Microsoft.Network/virtualNetworks", + "apiVersion": "2018-02-01", + "dependsOn": [ + "[resourceId('Microsoft.Network/ddosProtectionPlans', parameters('moodleCommon').ddosPlanName)]" + ], + "location": "[parameters('moodleCommon').location]", + "name": "[parameters('vnetName')]", + "properties": { + "addressSpace": { + "addressPrefixes": [ + "[parameters('vNetAddressSpace')]" + ] + }, + "ddosProtectionPlan": { + "id": "[resourceId('Microsoft.Network/ddosProtectionPlans', parameters('moodleCommon').ddosPlanName)]" + }, + "enableDdosProtection": "[parameters('moodleCommon').ddosSwitch]" + } + } + ], + "variables": { + "documentation01": "This sub-template creates a Azure DDoS protection plan and link it to a vnet" + } +} diff --git a/nested/network-vnet.json b/nested/network-vnet.json new file mode 100644 index 00000000..255553dd --- /dev/null +++ b/nested/network-vnet.json @@ -0,0 +1,30 @@ +{ + "$schema": "http://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "moodleCommon": { + "metadata": { + "description": "Common Moodle values" + }, + "type": "object" + } + }, + "resources": [ + { + "type": "Microsoft.Network/virtualNetworks", + "apiVersion": "2017-10-01", + "location": "[parameters('moodleCommon').location]", + "name": "[parameters('moodleCommon').vnetName]", + "properties": { + "addressSpace": { + "addressPrefixes": [ + "[concat(parameters('moodleCommon').vNetAddressSpace,'/16')]" + ] + } + } + } + ], + "variables": { + "documentation01": "This sub-template creates a virtual network when no customer BYO-vnet is specified" + } +} diff --git a/nested/network.json b/nested/network.json index 108fda70..bfdd130f 100644 --- a/nested/network.json +++ b/nested/network.json @@ -11,80 +11,97 @@ }, "resources": [ { - "apiVersion": "2015-06-15", - "location": "[resourceGroup().location]", - "name": "[parameters('moodleCommon').vnetName]", + "condition": "[equals(parameters('moodleCommon').customVnetId, '')]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2017-05-10", + "name": "vnetTemplate", "properties": { - "addressSpace": { - "addressPrefixes": [ - "[concat(parameters('moodleCommon').vNetAddressSpace,'/16')]" - ] + "mode": "Incremental", + "parameters": { + "moodleCommon": { + "value": "[parameters('moodleCommon')]" + } }, - "subnets": [ - { - "name": "[parameters('moodleCommon').subnetWeb]", - "properties": { - "addressPrefix": "[parameters('moodleCommon').subnetWebRange]" - } - }, - { - "name": "[parameters('moodleCommon').subnetSan]", - "properties": { - "addressPrefix": "[parameters('moodleCommon').subnetSanRange]" - } - }, - { - "name": "[parameters('moodleCommon').subnetRedis]", - "properties": { - "addressPrefix": "[parameters('moodleCommon').subnetRedisRange]" - } + "templateLink": { + "uri": "[concat(parameters('moodleCommon').baseTemplateUrl,'network-vnet.json',parameters('moodleCommon').artifactsSasToken)]" + } + } + }, + { + "apiVersion": "2018-02-01", + "condition": "[parameters('moodleCommon').ddosSwitch]", + "dependsOn": [ + "Microsoft.Resources/deployments/vnetTemplate" + ], + "name": "ddosTemplate", + "subscriptionId": "[variables('vnetSub')]", + "resourceGroup": "[variables('vnetRg')]", + "properties": { + "mode": "Incremental", + "parameters": { + "moodleCommon": { + "value": "[parameters('moodleCommon')]" }, - { - "name": "[parameters('moodleCommon').subnetElastic]", - "properties": { - "addressPrefix": "[parameters('moodleCommon').subnetElasticRange]" - } + "vnetName": { + "value": "[variables('vnetName')]" }, - { - "name": "[parameters('moodleCommon').gatewaySubnet]", - "properties": { - "addressPrefix": "[parameters('moodleCommon').gatewaySubnetRange]" - } + "vNetAddressSpace": { + "value": "[if(equals(parameters('moodleCommon').customVnetId, ''), concat(parameters('moodleCommon').vNetAddressSpace,'/16'), reference(parameters('moodleCommon').customVnetId, '2017-10-01').addressSpace.addressPrefixes[0])]" } - ] + }, + "templateLink": { + "uri": "[concat(parameters('moodleCommon').baseTemplateUrl,'network-vnet-ddos.json',parameters('moodleCommon').artifactsSasToken)]" + } }, - "type": "Microsoft.Network/virtualNetworks" + "type": "Microsoft.Resources/deployments" + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2017-05-10", + "dependsOn": [ + "Microsoft.Resources/deployments/vnetTemplate", + "Microsoft.Resources/deployments/ddosTemplate" + ], + "name": "subnetTemplate", + "subscriptionId": "[variables('vnetSub')]", + "resourceGroup": "[variables('vnetRg')]", + "properties": { + "mode": "Incremental", + "parameters": { + "moodleCommon": { + "value": "[parameters('moodleCommon')]" + }, + "vnetName": { + "value": "[variables('vnetName')]" + } + }, + "templateLink": { + "uri": "[concat(parameters('moodleCommon').baseTemplateUrl,'network-subnets.json',parameters('moodleCommon').artifactsSasToken)]" + } + } }, { - "apiVersion": "2015-06-15", - "location": "[resourceGroup().location]", + "condition": "[parameters('moodleCommon').vnetGwDeploySwitch]", + "type": "Microsoft.Network/publicIPAddresses", + "apiVersion": "2019-11-01", + "location": "[parameters('moodleCommon').location]", "name": "[parameters('moodleCommon').gatewayPublicIPName]", "properties": { "publicIPAllocationMethod": "Dynamic" }, "tags": { "displayName": "Virtual network gateway Public IP" - }, - "type": "Microsoft.Network/publicIPAddresses" - }, - { - "apiVersion": "2015-06-15", - "dependsOn": [ - "[resourceId('Microsoft.Network/virtualNetworks', parameters('moodleCommon').vnetName)]" - ], - "name": "[concat(parameters('moodleCommon').vnetName, '/', parameters('moodleCommon').gatewaySubnet)]", - "properties": { - "addressPrefix": "[parameters('moodleCommon').gatewaySubnetRange]" - }, - "type": "Microsoft.Network/virtualNetworks/subnets" + } }, { - "apiVersion": "2015-06-15", + "condition": "[parameters('moodleCommon').vnetGwDeploySwitch]", + "type": "Microsoft.Network/virtualNetworkGateways", + "apiVersion": "2017-10-01", "dependsOn": [ "[resourceId('Microsoft.Network/publicIPAddresses', parameters('moodleCommon').gatewayPublicIPName)]", - "[resourceId('Microsoft.Network/virtualNetworks/subnets', parameters('moodleCommon').vnetName, parameters('moodleCommon').gatewaySubnet)]" + "Microsoft.Resources/deployments/subnetTemplate" ], - "location": "[resourceGroup().location]", + "location": "[parameters('moodleCommon').location]", "name": "[parameters('moodleCommon').gatewayName]", "properties": { "activeActive": false, @@ -99,7 +116,7 @@ "id": "[resourceId('Microsoft.Network/publicIPAddresses', parameters('moodleCommon').gatewayPublicIPName)]" }, "subnet": { - "id": "[variables('gatewaySubnetRef')]" + "id": "[variables('subnetIdGateway')]" } } } @@ -110,12 +127,16 @@ "capacity": 2 }, "vpnType": "[parameters('moodleCommon').vpnType]" - }, - "type": "Microsoft.Network/virtualNetworkGateways" + } }, { - "apiVersion": "2015-06-15", - "location": "[resourceGroup().location]", + "condition": "[not(equals(parameters('moodleCommon').httpsTermination, 'AppGw'))]", + "type": "Microsoft.Network/publicIPAddresses", + "sku": { + "name": "[parameters('moodleCommon').lbSku]" + }, + "apiVersion": "2019-11-01", + "location": "[parameters('moodleCommon').location]", "name": "[parameters('moodleCommon').lbPipName]", "properties": { "dnsSettings": { @@ -125,15 +146,50 @@ }, "tags": { "displayName": "Load Balancer Public IP" + } + }, + { + "condition": "[equals(parameters('moodleCommon').httpsTermination, 'AppGw')]", + "type": "Microsoft.Network/publicIPAddresses", + "apiVersion": "2019-11-01", + "location": "[parameters('moodleCommon').location]", + "name": "[parameters('moodleCommon').appGwPipName]", + "sku" : { + "name" : "[if(endswith(parameters('moodleCommon').appGwSkuName,'v2'),'Standard','Basic')]" + }, + "properties": { + "dnsSettings": { + "domainNameLabel": "[parameters('moodleCommon').appGwName]" + }, + "publicIPAllocationMethod": "[if(endswith(parameters('moodleCommon').appGwSkuName,'v2'),'Static','Dynamic')]" + } + }, + { + "type": "Microsoft.Network/publicIPAddresses", + "apiVersion": "2019-11-01", + "location": "[parameters('moodleCommon').location]", + "name": "[parameters('moodleCommon').ctlrPipName]", + "properties": { + "dnsSettings": { + "domainNameLabel": "[parameters('moodleCommon').ctlrPipName]" + }, + "publicIPAllocationMethod": "Static" }, - "type": "Microsoft.Network/publicIPAddresses" + "tags": { + "displayName": "Controller VM Public IP" + } }, { - "apiVersion": "2016-03-30", + "condition": "[not(equals(parameters('moodleCommon').httpsTermination, 'AppGw'))]", + "type": "Microsoft.Network/loadBalancers", + "sku": { + "name": "[parameters('moodleCommon').lbSku]" + }, + "apiVersion": "2017-10-01", "dependsOn": [ "[concat('Microsoft.Network/publicIPAddresses/',parameters('moodleCommon').lbPipName)]" ], - "location": "[resourceGroup().location]", + "location": "[parameters('moodleCommon').location]", "name": "[parameters('moodleCommon').lbName]", "properties": { "backendAddressPools": [ @@ -146,7 +202,7 @@ "name": "[parameters('moodleCommon').extFeName ]", "properties": { "publicIPAddress": { - "id": "[variables('pipID')]" + "id": "[variables('lbPipID')]" } } } @@ -166,9 +222,9 @@ "frontendPort": 80, "idleTimeoutInMinutes": 5, "probe": { - "id": "[variables('extProbeID')]" + "id": "[variables('extProbeHTTPID')]" }, - "protocol": "tcp" + "protocol": "Tcp" } }, { @@ -185,45 +241,127 @@ "frontendPort": 443, "idleTimeoutInMinutes": 5, "probe": { - "id": "[variables('extProbeID')]" + "id": "[variables('extProbeHTTPSID')]" }, - "protocol": "tcp" + "protocol": "Tcp" } } ], "probes": [ { - "name": "[parameters('moodleCommon').extProbe ]", + "name": "[parameters('moodleCommon').extProbeHTTP ]", "properties": { - "intervalInSeconds": "5", - "numberOfProbes": "3", + "intervalInSeconds": 5, + "numberOfProbes": 3, "port": 80, - "protocol": "tcp" + "protocol": "Tcp" + } + }, + { + "name": "[parameters('moodleCommon').extProbeHTTPS ]", + "properties": { + "intervalInSeconds": 5, + "numberOfProbes": 3, + "port": 443, + "protocol": "Tcp" } } ] - }, - "type": "Microsoft.Network/loadBalancers" + } + }, + { + "condition": "[equals(parameters('moodleCommon').httpsTermination, 'AppGw')]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2017-05-10", + "dependsOn": [ + "Microsoft.Resources/deployments/subnetTemplate", + "[concat('Microsoft.Network/publicIPAddresses/',parameters('moodleCommon').appGwPipName)]" + ], + "name": "appGwTemplate", + "properties": { + "mode": "Incremental", + "parameters": { + "moodleCommon": { + "value": "[parameters('moodleCommon')]" + }, + "subnetIdAppGw": { + "value": "[variables('subnetIdAppGw')]" + }, + "sslCertData": { + "reference": { + "keyVault": { + "id": "[parameters('moodleCommon').appGwSslCertKeyVaultResourceId]" + }, + "secretName": "[parameters('moodleCommon').appGwSslCertKeyVaultSecretName]" + } + } + }, + "templateLink": { + "uri": "[concat(parameters('moodleCommon').baseTemplateUrl,'appgw.json',parameters('moodleCommon').artifactsSasToken)]" + } + } } ], "variables": { - "documentation01": "This sub-template creates a virtual network with three subnets and then creates the moodle load-balancer with public IP/dns", - "documentation02": "It expects certain values in the 'common' datastructure.", - "documentation03": " vnetName - name of virtual network", - "documentation04": " vNetAddressSpace - base of address of 16 bit address range", - "documentation05": " subnetWeb - name of subnet inside virtual network - will be assigned the .0.0/24 range", - "documentation06": " subnetSan - name of subnet inside virtual network - will be assigned the .1.0/24 range", - "documentation07": " subnetRedis - name of subnet inside virtual network - will be assigned the .3.0/24 range", - "documentation08": " subnetElastic - name of subnet inside virtual network - will be assigned the .4.0/24 range", - "documentation09": " gatewaySubnet - name of subnet inside virtual network - will be assigned the .2.0/24 range", - "documentation10": " lbPipName - name of public IP", - "documentation11": " lbName - name of Moodl load balancer", + "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", "extBeID": "[concat(variables('extLbID'),'/backendAddressPools/',parameters('moodleCommon').extBeName)]", "extFeID": "[concat(variables('extLbID'),'/frontendIPConfigurations/',parameters('moodleCommon').extFeName)]", "extLbID": "[resourceId('Microsoft.Network/loadBalancers',parameters('moodleCommon').lbName)]", - "extNatPoolID": "[concat(variables('extLbID'),'/inboundNatPools/',parameters('moodleCommon').extNatPool)]", - "extProbeID": "[concat(variables('extLbID'),'/probes/',parameters('moodleCommon').extProbe)]", - "gatewaySubnetRef": "[concat(resourceId('Microsoft.Network/virtualNetworks', parameters('moodleCommon').vnetName),'/subnets/',parameters('moodleCommon').gatewaySubnet)]", - "pipID": "[resourceId('Microsoft.Network/publicIPAddresses',parameters('moodleCommon').lbPipName)]" + "extProbeHTTPID": "[concat(variables('extLbID'),'/probes/',parameters('moodleCommon').extProbeHTTP)]", + "extProbeHTTPSID": "[concat(variables('extLbID'),'/probes/',parameters('moodleCommon').extProbeHTTPS)]", + "lbPipID": "[resourceId('Microsoft.Network/publicIPAddresses', parameters('moodleCommon').lbPipName)]", + "ctlrPipID": "[resourceId('Microsoft.Network/publicIPAddresses', parameters('moodleCommon').ctlrPipName)]", + "customVnetIdArr": "[split(parameters('moodleCommon').customVnetId, '/')]", + "vnetSub": "[if(equals(parameters('moodleCommon').customVnetId, ''), subscription().subscriptionId, variables('customVnetIdArr')[2])]", + "vnetRg": "[if(equals(parameters('moodleCommon').customVnetId, ''), resourceGroup().name, variables('customVnetIdArr')[4])]", + "vnetName": "[if(equals(parameters('moodleCommon').customVnetId, ''), parameters('moodleCommon').vnetName, variables('customVnetIdArr')[8])]", + "customVnetSubnetIdWeb": "[concat(parameters('moodleCommon').customVnetId, '/subnets/', parameters('moodleCommon').subnetWeb)]", + "customVnetSubnetIdSan": "[concat(parameters('moodleCommon').customVnetId, '/subnets/', parameters('moodleCommon').subnetSan)]", + "customVnetSubnetIdRedis": "[concat(parameters('moodleCommon').customVnetId, '/subnets/', parameters('moodleCommon').subnetRedis)]", + "customVnetSubnetIdElastic": "[concat(parameters('moodleCommon').customVnetId, '/subnets/', parameters('moodleCommon').subnetElastic)]", + "customVnetSubnetIdTika": "[concat(parameters('moodleCommon').customVnetId, '/subnets/', parameters('moodleCommon').subnetTika)]", + "customVnetSubnetIdGateway": "[concat(parameters('moodleCommon').customVnetId, '/subnets/', parameters('moodleCommon').subnetGateway)]", + "customVnetSubnetIdAppGw": "[concat(parameters('moodleCommon').customVnetId, '/subnets/', parameters('moodleCommon').subnetAppGw)]", + "subnetIdWeb": "[if(equals(parameters('moodleCommon').customVnetId, ''), resourceId('Microsoft.Network/virtualNetworks/subnets', variables('vnetName'), parameters('moodleCommon').subnetWeb), variables('customVnetSubnetIdWeb'))]", + "subnetIdSan": "[if(equals(parameters('moodleCommon').customVnetId, ''), resourceId('Microsoft.Network/virtualNetworks/subnets', variables('vnetName'), parameters('moodleCommon').subnetSan), variables('customVnetSubnetIdSan'))]", + "subnetIdRedis": "[if(equals(parameters('moodleCommon').customVnetId, ''), resourceId('Microsoft.Network/virtualNetworks/subnets', variables('vnetName'), parameters('moodleCommon').subnetRedis), variables('customVnetSubnetIdRedis'))]", + "subnetIdElastic": "[if(equals(parameters('moodleCommon').customVnetId, ''), resourceId('Microsoft.Network/virtualNetworks/subnets', variables('vnetName'), parameters('moodleCommon').subnetElastic), variables('customVnetSubnetIdElastic'))]", + "subnetIdTika": "[if(equals(parameters('moodleCommon').customVnetId, ''), resourceId('Microsoft.Network/virtualNetworks/subnets', variables('vnetName'), parameters('moodleCommon').subnetTika), variables('customVnetSubnetIdTika'))]", + "subnetIdGateway": "[if(equals(parameters('moodleCommon').customVnetId, ''), resourceId('Microsoft.Network/virtualNetworks/subnets', variables('vnetName'), parameters('moodleCommon').subnetGateway), variables('customVnetSubnetIdGateway'))]", + "subnetIdAppGw": "[if(equals(parameters('moodleCommon').customVnetId, ''), resourceId('Microsoft.Network/virtualNetworks/subnets', variables('vnetName'), parameters('moodleCommon').subnetAppGw), variables('customVnetSubnetIdAppGw'))]" + }, + "outputs": { + "lbPubIp": { + "value": "[if(equals(parameters('moodleCommon').httpsTermination, 'AppGw'), '0.0.0.0', reference(parameters('moodleCommon').lbPipName, '2017-10-01').ipAddress)]", + "type": "string" + }, + "ctlrPubIp": { + "value": "[reference(parameters('moodleCommon').ctlrPipName, '2017-10-01').ipAddress]", + "type": "string" + }, + "ctlrPubIpId": { + "value": "[variables('ctlrPipID')]", + "type": "string" + }, + "subnetIdWeb": { + "value": "[variables('subnetIdWeb')]", + "type": "string" + }, + "subnetIdSan": { + "value": "[variables('subnetIdSan')]", + "type": "string" + }, + "subnetIdRedis": { + "value": "[variables('subnetIdRedis')]", + "type": "string" + }, + "subnetIdElastic": { + "value": "[variables('subnetIdElastic')]", + "type": "string" + }, + "subnetIdTika": { + "value": "[variables('subnetIdTika')]", + "type": "string" + } } } diff --git a/nested/nfs-ha-vm.json b/nested/nfs-ha-vm.json new file mode 100644 index 00000000..8bc0eae1 --- /dev/null +++ b/nested/nfs-ha-vm.json @@ -0,0 +1,263 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "_artifactsLocation": { + "type": "string", + "metadata": { + "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." + } + }, + "_artifactsLocationSasToken": { + "type": "securestring", + "metadata": { + "description": "The sasToken required to access _artifactsLocation. When the template is deployed using the accompanying scripts, a sasToken will be automatically generated." + }, + "defaultValue": "" + }, + "vmIndex": { + "metadata": { + "description": "Index of the VM" + }, + "type": "int" + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Azure location where this template is to be deployed" + } + }, + "subnetId": { + "metadata": { + "description": "Azure resource ID of the subnet where this NFS-HA cluster is to be deployed" + }, + "type": "string" + }, + "ipAddrs": { + "metadata": { + "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" + }, + "type": "array" + }, + "nfsClientsIPRange": { + "metadata": { + "description": "IP range of the allowed NFS clients. E.g., 10.0.0.0/24" + }, + "type": "string" + }, + "enableAccelNwSwitch": { + "metadata": { + "description": "Switch to enable Azure Accelerated Networking (Note: this feature is NOT available for D1-level VM SKU)" + }, + "type": "bool", + "defaultValue": false + }, + "availSetId": { + "metadata": { + "description": "Azure resource ID of the availability set where this VM is to be deployed" + }, + "type": "string" + }, + "vmSku": { + "metadata": { + "description": "Azure VM SKU for the NFS HA VMs" + }, + "type": "string", + "defaultValue": "Standard_DS2_v2" + }, + "adminUserName": { + "metadata": { + "description": "VM admin user name" + }, + "type": "string", + "defaultValue": "azureadmin" + }, + "sshPublicKey": { + "metadata": { + "description": "SSH public key for the admin user" + }, + "type": "string" + }, + "osType": { + "metadata": { + "description": "OS type (offer/publisher/sku/version) info" + }, + "type": "object" + }, + "osDiskStorageType": { + "defaultValue": "Premium_LRS", + "allowedValues": [ + "Premium_LRS", + "Standard_LRS" + ], + "metadata": { + "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." + }, + "type": "string" + }, + "dataDiskCountPerVM": { + "metadata": { + "description": "Number of data disks per VM. 2 or more disks will be configured as RAID0" + }, + "defaultValue": 1, + "minValue": 1, + "maxValue": 8, + "type": "int" + }, + "dataDiskSizeInGB": { + "defaultValue": 32, + "metadata": { + "description": "Size of each disk in an NFS server" + }, + "type": "int" + }, + "resourcesUniqueString": { + "metadata": { + "description": "Unique string of fixed length (e.g., 6) identifying related resources" + }, + "type": "string", + "defaultValue": "[substring(uniqueString(resourceGroup().id, deployment().name), 3, 6)]" + }, + "lbBeId": { + "metadata": { + "description": "Azure resource ID of the load balancer backend pool to which this VM's NIC should be added" + }, + "type": "string" + } + }, + "resources": [ + { + "type": "Microsoft.Network/networkInterfaces", + "apiVersion": "2017-10-01", + "location": "[parameters('location')]", + "name": "[variables('nicName')]", + "properties": { + "ipConfigurations": [ + { + "name": "[variables('ipcfgName')]", + "properties": { + "privateIPAllocationMethod": "Static", + "privateIPAddress": "[parameters('ipAddrs')[parameters('vmIndex')]]", + "subnet": { + "id": "[parameters('subnetId')]" + }, + "loadBalancerBackendAddressPools": [ + { + "id": "[parameters('lbBeId')]" + } + ] + } + } + ], + "enableAcceleratedNetworking": "[parameters('enableAccelNwSwitch')]" + }, + "tags": { + "displayName": "[concat('NIC for NFS-HA node', parameters('vmIndex'), ' VM')]" + } + }, + { + "type": "Microsoft.Compute/virtualMachines", + "apiVersion": "2017-03-30", + "dependsOn": [ + "[concat('Microsoft.Network/networkInterfaces/', variables('nicName'))]" + ], + "location": "[parameters('location')]", + "name": "[variables('vmResourceName')]", + "properties": { + "availabilitySet": { + "id": "[parameters('availSetId')]" + }, + "hardwareProfile": { + "vmSize": "[parameters('vmSku')]" + }, + "networkProfile": { + "networkInterfaces": [ + { + "id": "[resourceId('Microsoft.Network/networkInterfaces', variables('nicName'))]" + } + ] + }, + "osProfile": { + "adminUsername": "[parameters('adminUserName')]", + "computerName": "[variables('vmName')]", + "linuxConfiguration": { + "disablePasswordAuthentication": true, + "ssh": { + "publicKeys": [ + { + "path": "[concat('/home/', parameters('adminUserName'), '/.ssh/authorized_keys')]", + "keyData": "[parameters('sshPublicKey')]" + } + ] + } + } + }, + "storageProfile": { + "imageReference": "[parameters('osType')]", + "osDisk": { + "createOption": "FromImage", + "managedDisk": { + "storageAccountType": "[parameters('osDiskStorageType')]" + }, + "name": "[concat(variables('vmResourceName'), '_osDisk')]" + }, + "copy": [ + { + "name": "dataDisks", + "count": "[parameters('dataDiskCountPerVM')]", + "input": { + "managedDisk": { + "storageAccountType": "Premium_LRS" + }, + "diskSizeGB": "[parameters('dataDiskSizeInGB')]", + "lun": "[copyIndex('dataDisks')]", + "createOption": "Empty" + } + } + ] + } + }, + "resources": [ + { + "type": "extensions", + "apiVersion": "2017-03-30", + "dependsOn": [ + "[concat('Microsoft.Compute/virtualMachines/', variables('vmResourceName'))]" + ], + "location": "[parameters('location')]", + "name": "setup_nfs_ha", + "properties": { + "publisher": "Microsoft.Azure.Extensions", + "settings": { + "fileUris": [ + "[variables('scriptUri')]", + "[variables('commonFunctionsScriptUri')]" + ] + }, + "protectedSettings":{ + "commandToExecute": "[variables('cmdExec')]" + }, + "type": "CustomScript", + "typeHandlerVersion": "2.0" + }, + "tags": { + "displayName": "NFS-HA VM setup CustomScript extension" + } + } + ], + "tags": { + "displayName": "[concat('NFS-HA Virtual Machine ', variables('vmName'))]" + } + } + ], + "variables": { + "nicName": "[concat('nfs-ha-nic', parameters('vmIndex'), '-', parameters('resourcesUniqueString'))]", + "ipCfgName": "[concat('nfs-ha-ipcfg', parameters('vmIndex'))]", + "vmResourceName": "[concat('nfs-ha-vm', parameters('vmIndex'), '-', parameters('resourcesUniqueString'))]", + "vmName": "[concat('hanode', parameters('vmIndex'), '-', parameters('resourcesUniqueString'))]", + "scriptUri": "[concat(parameters('_artifactsLocation'), 'scripts/setup_nfs_ha.sh', parameters('_artifactsLocationSasToken'))]", + "commonFunctionsScriptUri": "[concat(parameters('_artifactsLocation'), 'scripts/helper_functions.sh', parameters('_artifactsLocationSasToken'))]", + "cmdExec": "[concat('bash -x setup_nfs_ha.sh hanode0-', parameters('resourcesUniqueString'), ' ', parameters('ipAddrs')[0], ' hanode1-', parameters('resourcesUniqueString'), ' ', parameters('ipAddrs')[1], ' ', parameters('nfsClientsIPRange'))]" + } +} diff --git a/nested/nfs-ha.json b/nested/nfs-ha.json new file mode 100644 index 00000000..e09a2ab8 --- /dev/null +++ b/nested/nfs-ha.json @@ -0,0 +1,583 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "_artifactsLocation": { + "type": "string", + "metadata": { + "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." + } + }, + "_artifactsLocationSasToken": { + "type": "securestring", + "metadata": { + "description": "The sasToken required to access _artifactsLocation. When the template is deployed using the accompanying scripts, a sasToken will be automatically generated." + }, + "defaultValue": "" + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Azure location where this template is to be deployed" + } + }, + "subnetId": { + "metadata": { + "description": "Azure resource ID of the subnet where this NFS-HA cluster is to be deployed" + }, + "type": "string" + }, + "node0IPAddr": { + "metadata": { + "description": "IP address of node 0 (statically assigned). E.g., 10.0.0.11. Must belong to the IP range of the specified subnet" + }, + "type": "string" + }, + "node1IPAddr": { + "metadata": { + "description": "IP address of node 1 (statically assigned). E.g., 10.0.0.22. Must belong to the IP range of the specified subnet" + }, + "type": "string" + }, + "nfsClientsIPRange": { + "metadata": { + "description": "IP range of the allowed NFS clients. E.g., 10.0.0.0/24" + }, + "type": "string" + }, + "lbFrontEndIpAddr": { + "metadata": { + "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" + }, + "type": "string" + }, + "enableAccelNwSwitch": { + "metadata": { + "description": "Switch to enable Azure Accelerated Networking (Note: this feature is NOT available for D1-level VM SKU)" + }, + "type": "bool", + "defaultValue": false + }, + "vmSku": { + "metadata": { + "description": "Azure VM SKU for the NFS HA VMs" + }, + "type": "string", + "defaultValue": "Standard_DS2_v2" + }, + "adminUserName": { + "metadata": { + "description": "VM admin user name" + }, + "type": "string", + "defaultValue": "azureadmin" + }, + "sshPublicKey": { + "metadata": { + "description": "SSH public key for the admin user" + }, + "type": "string" + }, + "osType": { + "metadata": { + "description": "OS type (offer/publisher/sku/version) info" + }, + "type": "object", + "defaultValue": { + "offer": "UbuntuServer", + "publisher": "Canonical", + "sku": "16.04-LTS", + "version": "latest" + } + }, + "osDiskStorageType": { + "defaultValue": "Premium_LRS", + "allowedValues": [ + "Premium_LRS", + "Standard_LRS" + ], + "metadata": { + "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." + }, + "type": "string" + }, + "dataDiskCountPerVM": { + "metadata": { + "description": "Number of data disks per VM. 2 or more disks will be configured as RAID0" + }, + "defaultValue": 1, + "minValue": 1, + "maxValue": 8, + "type": "int" + }, + "dataDiskSizeInGB": { + "defaultValue": 32, + "metadata": { + "description": "Size per disk in an NFS server" + }, + "type": "int" + }, + "resourcesUniqueString": { + "metadata": { + "description": "Unique string of fixed length (e.g., 6) identifying related resources" + }, + "type": "string", + "defaultValue": "[substring(uniqueString(resourceGroup().id, deployment().name), 3, 6)]" + } + }, + "resources": [ + { + "type": "Microsoft.Compute/availabilitySets", + "apiVersion": "2017-03-30", + "location": "[parameters('location')]", + "name": "[variables('availSetName')]", + "properties": { + "platformFaultDomainCount": 2, + "platformUpdateDomainCount": 5 + }, + "sku": { + "name": "Aligned" + }, + "tags": { + "displayName": "NFS-HA Availability Set" + } + }, + { + "type": "Microsoft.Network/loadBalancers", + "sku": { + "name": "Basic" + }, + "apiVersion": "2017-10-01", + "location": "[parameters('location')]", + "name": "[variables('nfsHaLbName')]", + "properties": { + "frontendIPConfigurations": [ + { + "name": "[variables('nfsHaLbFeName')]", + "properties": { + "privateIPAddress": "[parameters('lbFrontEndIpAddr')]", + "privateIPAllocationMethod": "Static", + "subnet": { + "id": "[parameters('subnetId')]" + } + } + } + ], + "backendAddressPools": [ + { + "name": "[variables('nfsHaLbBeName')]" + } + ], + "loadBalancingRules": [ + { + "name": "[concat(variables('nfsHaLbRuleName'), '-nfsd-tcp')]", + "properties": { + "frontendIPConfiguration": { + "id": "[variables('nfsHaLbFeId')]" + }, + "frontendPort": 2049, + "backendAddressPool": { + "id": "[variables('nfsHaLbBeId')]" + }, + "backendPort": 2049, + "protocol": "Tcp", + "probe": { + "id": "[variables('nfsHaLbProbeId')]" + }, + "enableFloatingIP": false, + "idleTimeoutInMinutes": 4 + } + }, + { + "name": "[concat(variables('nfsHaLbRuleName'), '-nfsd-udp')]", + "properties": { + "frontendIPConfiguration": { + "id": "[variables('nfsHaLbFeId')]" + }, + "frontendPort": 2049, + "backendAddressPool": { + "id": "[variables('nfsHaLbBeId')]" + }, + "backendPort": 2049, + "protocol": "Udp", + "probe": { + "id": "[variables('nfsHaLbProbeId')]" + }, + "enableFloatingIP": false, + "idleTimeoutInMinutes": 4 + } + }, + { + "name": "[concat(variables('nfsHaLbRuleName'), '-rpcbind-tcp')]", + "properties": { + "frontendIPConfiguration": { + "id": "[variables('nfsHaLbFeId')]" + }, + "frontendPort": 111, + "backendAddressPool": { + "id": "[variables('nfsHaLbBeId')]" + }, + "backendPort": 111, + "protocol": "Tcp", + "probe": { + "id": "[variables('nfsHaLbProbeId')]" + }, + "enableFloatingIP": false, + "idleTimeoutInMinutes": 4 + } + }, + { + "name": "[concat(variables('nfsHaLbRuleName'), '-rpcbind-udp')]", + "properties": { + "frontendIPConfiguration": { + "id": "[variables('nfsHaLbFeId')]" + }, + "frontendPort": 111, + "backendAddressPool": { + "id": "[variables('nfsHaLbBeId')]" + }, + "backendPort": 111, + "protocol": "Udp", + "probe": { + "id": "[variables('nfsHaLbProbeId')]" + }, + "enableFloatingIP": false, + "idleTimeoutInMinutes": 4 + } + }, + { + "name": "[concat(variables('nfsHaLbRuleName'), '-mountd-tcp')]", + "properties": { + "frontendIPConfiguration": { + "id": "[variables('nfsHaLbFeId')]" + }, + "frontendPort": 2000, + "backendAddressPool": { + "id": "[variables('nfsHaLbBeId')]" + }, + "backendPort": 2000, + "protocol": "Tcp", + "probe": { + "id": "[variables('nfsHaLbProbeId')]" + }, + "enableFloatingIP": false, + "idleTimeoutInMinutes": 4 + } + }, + { + "name": "[concat(variables('nfsHaLbRuleName'), '-mountd-udp')]", + "properties": { + "frontendIPConfiguration": { + "id": "[variables('nfsHaLbFeId')]" + }, + "frontendPort": 2000, + "backendAddressPool": { + "id": "[variables('nfsHaLbBeId')]" + }, + "backendPort": 2000, + "protocol": "Udp", + "probe": { + "id": "[variables('nfsHaLbProbeId')]" + }, + "enableFloatingIP": false, + "idleTimeoutInMinutes": 4 + } + }, + { + "name": "[concat(variables('nfsHaLbRuleName'), '-statd-tcp')]", + "properties": { + "frontendIPConfiguration": { + "id": "[variables('nfsHaLbFeId')]" + }, + "frontendPort": 2001, + "backendAddressPool": { + "id": "[variables('nfsHaLbBeId')]" + }, + "backendPort": 2001, + "protocol": "Tcp", + "probe": { + "id": "[variables('nfsHaLbProbeId')]" + }, + "enableFloatingIP": false, + "idleTimeoutInMinutes": 4 + } + }, + { + "name": "[concat(variables('nfsHaLbRuleName'), '-statd-udp')]", + "properties": { + "frontendIPConfiguration": { + "id": "[variables('nfsHaLbFeId')]" + }, + "frontendPort": 2001, + "backendAddressPool": { + "id": "[variables('nfsHaLbBeId')]" + }, + "backendPort": 2001, + "protocol": "Udp", + "probe": { + "id": "[variables('nfsHaLbProbeId')]" + }, + "enableFloatingIP": false, + "idleTimeoutInMinutes": 4 + } + }, + { + "name": "[concat(variables('nfsHaLbRuleName'), '-statd-outgoing-tcp')]", + "properties": { + "frontendIPConfiguration": { + "id": "[variables('nfsHaLbFeId')]" + }, + "frontendPort": 2002, + "backendAddressPool": { + "id": "[variables('nfsHaLbBeId')]" + }, + "backendPort": 2002, + "protocol": "Tcp", + "probe": { + "id": "[variables('nfsHaLbProbeId')]" + }, + "enableFloatingIP": false, + "idleTimeoutInMinutes": 4 + } + }, + { + "name": "[concat(variables('nfsHaLbRuleName'), '-statd-outgoing-udp')]", + "properties": { + "frontendIPConfiguration": { + "id": "[variables('nfsHaLbFeId')]" + }, + "frontendPort": 2002, + "backendAddressPool": { + "id": "[variables('nfsHaLbBeId')]" + }, + "backendPort": 2002, + "protocol": "Udp", + "probe": { + "id": "[variables('nfsHaLbProbeId')]" + }, + "enableFloatingIP": false, + "idleTimeoutInMinutes": 4 + } + }, + { + "name": "[concat(variables('nfsHaLbRuleName'), '-quotad-tcp')]", + "properties": { + "frontendIPConfiguration": { + "id": "[variables('nfsHaLbFeId')]" + }, + "frontendPort": 2003, + "backendAddressPool": { + "id": "[variables('nfsHaLbBeId')]" + }, + "backendPort": 2003, + "protocol": "Tcp", + "probe": { + "id": "[variables('nfsHaLbProbeId')]" + }, + "enableFloatingIP": false, + "idleTimeoutInMinutes": 4 + } + }, + { + "name": "[concat(variables('nfsHaLbRuleName'), '-quotad-udp')]", + "properties": { + "frontendIPConfiguration": { + "id": "[variables('nfsHaLbFeId')]" + }, + "frontendPort": 2003, + "backendAddressPool": { + "id": "[variables('nfsHaLbBeId')]" + }, + "backendPort": 2003, + "protocol": "Udp", + "probe": { + "id": "[variables('nfsHaLbProbeId')]" + }, + "enableFloatingIP": false, + "idleTimeoutInMinutes": 4 + } + }, + { + "name": "[concat(variables('nfsHaLbRuleName'), '-lockd-tcp')]", + "properties": { + "frontendIPConfiguration": { + "id": "[variables('nfsHaLbFeId')]" + }, + "frontendPort": 2004, + "backendAddressPool": { + "id": "[variables('nfsHaLbBeId')]" + }, + "backendPort": 2004, + "protocol": "Tcp", + "probe": { + "id": "[variables('nfsHaLbProbeId')]" + }, + "enableFloatingIP": false, + "idleTimeoutInMinutes": 4 + } + }, + { + "name": "[concat(variables('nfsHaLbRuleName'), '-lockd-udp')]", + "properties": { + "frontendIPConfiguration": { + "id": "[variables('nfsHaLbFeId')]" + }, + "frontendPort": 2004, + "backendAddressPool": { + "id": "[variables('nfsHaLbBeId')]" + }, + "backendPort": 2004, + "protocol": "Udp", + "probe": { + "id": "[variables('nfsHaLbProbeId')]" + }, + "enableFloatingIP": false, + "idleTimeoutInMinutes": 4 + } + }, + { + "name": "[concat(variables('nfsHaLbRuleName'), '-nfs-callback-tcp')]", + "properties": { + "frontendIPConfiguration": { + "id": "[variables('nfsHaLbFeId')]" + }, + "frontendPort": 2005, + "backendAddressPool": { + "id": "[variables('nfsHaLbBeId')]" + }, + "backendPort": 2005, + "protocol": "Tcp", + "probe": { + "id": "[variables('nfsHaLbProbeId')]" + }, + "enableFloatingIP": false, + "idleTimeoutInMinutes": 4 + } + }, + { + "name": "[concat(variables('nfsHaLbRuleName'), '-nfs-callback-udp')]", + "properties": { + "frontendIPConfiguration": { + "id": "[variables('nfsHaLbFeId')]" + }, + "frontendPort": 2005, + "backendAddressPool": { + "id": "[variables('nfsHaLbBeId')]" + }, + "backendPort": 2005, + "protocol": "Udp", + "probe": { + "id": "[variables('nfsHaLbProbeId')]" + }, + "enableFloatingIP": false, + "idleTimeoutInMinutes": 4 + } + } + ], + "probes": [ + { + "name": "[variables('nfsHaLbProbeName')]", + "properties": { + "intervalInSeconds": 5, + "numberOfProbes": 2, + "port": 61000, + "protocol": "Tcp" + } + } + ] + } + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2017-05-10", + "copy": { + "count": 2, + "name": "nfs-ha-vm-loop" + }, + "dependsOn": [ + "[concat('Microsoft.Compute/availabilitySets/', variables('availSetName'))]", + "[concat('Microsoft.Network/loadBalancers/', variables('nfsHaLbName'))]" + ], + "name": "[concat(variables('vmDeploymentNameBase'), copyIndex())]", + "properties": { + "mode": "Incremental", + "parameters": { + "_artifactsLocation": { + "value": "[parameters('_artifactsLocation')]" + }, + "_artifactsLocationSasToken": { + "value": "[parameters('_artifactsLocationSasToken')]" + }, + "location": { + "value": "[parameters('location')]" + }, + "vmIndex": { + "value": "[copyindex()]" + }, + "ipAddrs": { + "value": "[variables('nodeIpAddrs')]" + }, + "nfsClientsIPRange": { + "value": "[parameters('nfsClientsIPRange')]" + }, + "subnetId": { + "value": "[parameters('subnetId')]" + }, + "enableAccelNwSwitch": { + "value": "[parameters('enableAccelNwSwitch')]" + }, + "availSetId": { + "value": "[resourceId('Microsoft.Compute/AvailabilitySets', variables('availSetName'))]" + }, + "vmSku": { + "value": "[parameters('vmSku')]" + }, + "adminUserName": { + "value": "[parameters('adminUserName')]" + }, + "sshPublicKey": { + "value": "[parameters('sshPublicKey')]" + }, + "osType": { + "value": "[parameters('osType')]" + }, + "osDiskStorageType": { + "value": "[parameters('osDiskStorageType')]" + }, + "dataDiskCountPerVM": { + "value": "[parameters('dataDiskCountPerVM')]" + }, + "dataDiskSizeInGB": { + "value": "[parameters('dataDiskSizeInGB')]" + }, + "resourcesUniqueString": { + "value": "[parameters('resourcesUniqueString')]" + }, + "lbBeId": { + "value": "[variables('nfsHaLbBeId')]" + } + }, + "templateLink": { + "uri": "[concat(parameters('_artifactsLocation'), 'nested/nfs-ha-vm.json', parameters('_artifactsLocationSasToken'))]" + } + } + } + ], + "variables": { + "availSetName": "[concat('nfs-ha-availset-', parameters('resourcesUniqueString'))]", + "vmDeploymentNameBase": "nfs-ha-vm-deployment", + "nodeIpAddrs": [ + "[parameters('node0IPAddr')]", + "[parameters('node1IPAddr')]" + ], + "nfsHaLbName": "[concat('nfs-ha-lb-', parameters('resourcesUniqueString'))]", + "nfsHaLbFeName": "nfs-ha-lb-fe", + "nfsHaLbFeId": "[resourceId('Microsoft.Network/loadBalancers/frontendIPConfigurations', variables('nfsHaLbName'), variables('nfsHaLbFeName'))]", + "nfsHaLbBeName": "nfs-ha-lb-be", + "nfsHaLbBeId": "[resourceId('Microsoft.Network/loadBalancers/backendAddressPools', variables('nfsHaLbName'), variables('nfsHaLbBeName'))]", + "nfsHaLbRuleName": "nfs-ha-lb-rule", + "nfsHaLbProbeName": "nfs-ha-lb-probe", + "nfsHaLbProbeId": "[resourceId('Microsoft.Network/loadBalancers/probes', variables('nfsHaLbName'), variables('nfsHaLbProbeName'))]" + } +} diff --git a/nested/postgres.json b/nested/postgres.json deleted file mode 100644 index e38309d0..00000000 --- a/nested/postgres.json +++ /dev/null @@ -1,63 +0,0 @@ -{ - "$schema": "http://schema.management.azure.com/schemas/2014-04-01-preview/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "parameters": { - "moodleCommon": { - "metadata": { - "description": "Common Moodle values" - }, - "type": "object" - } - }, - "resources": [ - { - "apiVersion": "2016-02-01-privatepreview", - "kind": "", - "location": "[resourceGroup().location]", - "name": "[parameters('moodleCommon').serverName]", - "properties": { - "administratorLogin": "[parameters('moodleCommon').dbLogin]", - "administratorLoginPassword": "[parameters('moodleCommon').dbLoginPassword]", - "sslEnforcement": "[parameters('moodleCommon').sslEnforcement]", - "storageMB": "[parameters('moodleCommon').skuSizeMB]", - "version": "[parameters('moodleCommon').postgresVersion]" - }, - "resources": [ - { - "apiVersion": "2016-02-01-privatepreview", - "dependsOn": [ - "[concat('Microsoft.DBforPostgreSQL/servers/', parameters('moodleCommon').serverName)]" - ], - "location": "[resourceGroup().location]", - "name": "[parameters('moodleCommon').firewallRuleName]", - "properties": { - "startIpAddress": "0.0.0.0", - "endIpAddress": "255.255.255.255" - }, - "type": "firewallrules" - } - ], - "sku": { - "capacity": "[parameters('moodleCommon').skuCapacityDTU]", - "family": "[parameters('moodleCommon').skuFamily]", - "name": "[parameters('moodleCommon').skuName]", - "size": "[parameters('moodleCommon').skuSizeMB]", - "tier": "[parameters('moodleCommon').skuTier]" - }, - "type": "Microsoft.DBforPostgreSQL/servers" - } - ], - "variables": { - "documentation1": "This sub-template creates a postgresql server. It expects certain values in the 'common' datastructure.", - "documentation10": " serverName - Postgresql server name", - "documentation11": " postgresVersion - Postgresql version", - "documentation2": " administratorLogin - postgresql admin username", - "documentation3": " administratorLoginPassword - postgresql admin password", - "documentation4": " location - Postgresql server location", - "documentation5": " skuCapacityDTU - Postgresql database trasaction units", - "documentation6": " skuFamily - Postgresql sku family", - "documentation7": " skuName - Postgresql sku name", - "documentation8": " skuSizeMB - Postgresql sku size in mb", - "documentation9": " skuTier - Postgresql sku tier" - } -} diff --git a/nested/recoveryservices1.json b/nested/recoveryservices.json similarity index 92% rename from nested/recoveryservices1.json rename to nested/recoveryservices.json index 43069b01..60895951 100644 --- a/nested/recoveryservices1.json +++ b/nested/recoveryservices.json @@ -11,22 +11,23 @@ }, "resources": [ { - "apiVersion": "2015-11-10", - "location": "[resourceGroup().location]", + "type": "Microsoft.RecoveryServices/vaults", + "apiVersion": "2016-06-01", + "location": "[parameters('moodleCommon').location]", "name": "[parameters('moodleCommon').vaultName]", "properties": {}, "sku": { "name": "RS0", "tier": "Standard" - }, - "type": "Microsoft.RecoveryServices/vaults" + } }, { - "apiVersion": "2015-11-10", + "type": "Microsoft.RecoveryServices/vaults/backupPolicies", + "apiVersion": "2017-07-01", "dependsOn": [ "[concat('Microsoft.RecoveryServices/vaults/', parameters('moodleCommon').vaultName)]" ], - "location": "[resourceGroup().location]", + "location": "[parameters('moodleCommon').location]", "name": "[concat(parameters('moodleCommon').vaultName, '/', parameters('moodleCommon').policyName)]", "properties": { "backupManagementType": "AzureIaasVM", @@ -71,8 +72,7 @@ "scheduleRunFrequency": "Daily", "scheduleRunTimes": "[variables('scheduleRunTimes')]" } - }, - "type": "Microsoft.RecoveryServices/vaults/backupPolicies" + } } ], "variables": { diff --git a/nested/recoveryservices0.json b/nested/recoveryservices0.json deleted file mode 100644 index eb1cf24a..00000000 --- a/nested/recoveryservices0.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "parameters": { - "moodleCommon": { - "metadata": { - "description": "Common Moodle values" - }, - "type": "object" - } - }, - "resources": [], - "variables": { - "documentation1": "This sub-template intentionally blank - a blank template allows for an optional exectuion strategy", - "documentation2": "", - "documentation3": "You should look for a template of the same name, but with a 1 instead of a 0 (at the end)" - } -} diff --git a/nested/recoveryservicesEnlist1.json b/nested/recoveryservicesEnlist.json similarity index 93% rename from nested/recoveryservicesEnlist1.json rename to nested/recoveryservicesEnlist.json index cbde85ba..b110aa35 100644 --- a/nested/recoveryservicesEnlist1.json +++ b/nested/recoveryservicesEnlist.json @@ -17,15 +17,15 @@ }, "resources": [ { + "type": "Microsoft.RecoveryServices/vaults/backupFabrics/protectionContainers/protectedItems", "apiVersion": "2016-06-01", - "location": "[resourceGroup().location]", + "location": "[parameters('moodleCommon').location]", "name": "[concat(parameters('moodleCommon').vaultName, '/', variables('backupFabric'), '/', variables('v2VmContainer'), concat(resourceGroup().name,';',parameters('vmName')), '/', variables('v2Vm'), concat(resourceGroup().name,';',parameters('vmName')))]", "properties": { "policyId": "[resourceId('Microsoft.RecoveryServices/vaults/backupPolicies',parameters('moodleCommon').vaultName,parameters('moodleCommon').policyName )]", "protectedItemType": "[variables('v2VmType')]", "sourceResourceId": "[resourceId(subscription().subscriptionId,resourceGroup().name,'Microsoft.Compute/virtualMachines',parameters('vmName'))]" - }, - "type": "Microsoft.RecoveryServices/vaults/backupFabrics/protectionContainers/protectedItems" + } } ], "variables": { diff --git a/nested/recoveryservicesEnlist0.json b/nested/recoveryservicesEnlist0.json deleted file mode 100644 index 3709acbf..00000000 --- a/nested/recoveryservicesEnlist0.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "parameters": { - "moodleCommon": { - "metadata": { - "description": "Common Moodle values" - }, - "type": "object" - }, - "vmName": { - "metadata": { - "description": "Name of VM to enlist in AzureBackup" - }, - "type": "string" - } - }, - "resources": [], - "variables": { - "documentation1": "This sub-template intentionally blank - a blank template allows for an optional exectuion strategy", - "documentation2": "", - "documentation3": "You should look for a template of the same name, but with a 1 instead of a 0 (at the end)" - } -} diff --git a/nested/redis.json b/nested/redis.json index 8fe832f0..e1ab17c4 100644 --- a/nested/redis.json +++ b/nested/redis.json @@ -7,12 +7,19 @@ "description": "Common Moodle values" }, "type": "object" + }, + "subnetIdRedis": { + "metadata": { + "description": "Azure resource ID of the subnet where this Redis instance is to be deployed" + }, + "type": "string" } }, "resources": [ { + "type": "Microsoft.Cache/Redis", "apiVersion": "2016-04-01", - "location": "[resourceGroup().location]", + "location": "[parameters('moodleCommon').location]", "name": "[parameters('moodleCommon').redisCacheName]", "properties": { "enableNonSslPort": true, @@ -21,18 +28,17 @@ "family": "P", "name": "Premium" }, - "subnetId": "[concat(resourceId('Microsoft.Network/virtualNetworks', parameters('moodleCommon').vnetName), '/subnets/', parameters('moodleCommon').subnetRedis)]" - }, - "type": "Microsoft.Cache/Redis" + "subnetId": "[parameters('subnetIdRedis')]" + } } ], "variables": { "documentation1": "This sub-template creates a redis cache. It expects certain values in the 'common' datastructure.", "documentation2": " redisCacheName - name of cache", - "redisResourceId": "[concat(resourceGroup().id,'/providers/Microsoft.Cache/Redis/', parameters('moodleCommon').redisCacheName)]" + "redisResourceId": "[resourceId('Microsoft.Cache/Redis', parameters('moodleCommon').redisCacheName)]" }, "outputs": { - "redisPrimaryKey": { + "redisKey": { "value": "[listKeys(variables('redisResourceId'), '2016-04-01').primaryKey]", "type": "string" } diff --git a/nested/search-azure.json b/nested/search-azure.json new file mode 100644 index 00000000..95fb6af0 --- /dev/null +++ b/nested/search-azure.json @@ -0,0 +1,50 @@ +{ + "$schema": "http://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "moodleCommon": { + "metadata": { + "description": "Common Moodle values" + }, + "type": "object" + }, + "subnetIdElastic": { + "metadata": { + "description": "Azure resource ID of the subnet where the Elastic Search VMs are to be deployed (if any)" + }, + "type": "string" + } + }, + "resources": [ + { + "type": "Microsoft.Search/searchServices", + "apiVersion": "2015-08-19", + "location": "[parameters('moodleCommon').location]", + "name": "[parameters('moodleCommon').azureSearchName]", + "sku": { + "name": "[parameters('moodleCommon').azureSearchSku]" + }, + "properties": { + "replicaCount": "[parameters('moodleCommon').azureSearchReplicaCount]", + "partitionCount": "[parameters('moodleCommon').azureSearchPartitionCount]", + "hostingMode": "[parameters('moodleCommon').azureSearchHostingMode]" + } + } + ], + "variables": { + "documentation01": "This sub-template drives the Azure Search which is used as the access-point for other moodle VM's ", + "documentation02": "It expects certain values in the 'common' datastructure.", + "documentation03": " azureSearchName - the name of the Azure Search.", + "documentation04": " azureSearchSku - the level of Azure Search service.", + "documentation06": " azureSearchReplicaCount - number of Azure Search replicas.", + "documentation07": " azureSearchPartitionCount - number of Azure Search partitions.", + "documentation08": " azureSearchHostingMode - the type of Azure Search hosting mode.", + "azureSearchServiceId": "[resourceId('Microsoft.Search/searchServices', parameters('moodleCommon').azureSearchName)]" + }, + "outputs": { + "azureSearchKey": { + "value": "[listAdminKeys(variables('azureSearchServiceId'), '2015-08-19').PrimaryKey]", + "type": "string" + } + } +} diff --git a/nested/elasticconfig1.json b/nested/search-elastic-config.json similarity index 69% rename from nested/elasticconfig1.json rename to nested/search-elastic-config.json index 618d8c48..36c7bda3 100644 --- a/nested/elasticconfig1.json +++ b/nested/search-elastic-config.json @@ -11,76 +11,82 @@ }, "resources": [ { - "apiVersion": "2015-06-15", - "location": "[resourceGroup().location]", + "type": "Microsoft.Compute/virtualMachines/extensions", + "apiVersion": "2017-03-30", + "location": "[parameters('moodleCommon').location]", "name": "[concat(parameters('moodleCommon').elasticVmName1,'/','install_elastic')]", "properties": { "autoUpgradeMinorVersion": true, - "publisher": "Microsoft.OSTCExtensions", + "publisher": "Microsoft.Azure.Extensions", "settings": { - "commandToExecute": "[variables('cmdExec')]", "fileUris": [ "[variables('scriptUri')]" ] }, - "type": "CustomScriptForLinux", - "typeHandlerVersion": "1.4" + "protectedSettings":{ + "commandToExecute": "[variables('cmdExec')]" + }, + "type": "CustomScript", + "typeHandlerVersion": "2.0" }, "tags": { "displayName": "install_elastic" - }, - "type": "Microsoft.Compute/virtualMachines/extensions" + } }, { - "apiVersion": "2015-06-15", - "location": "[resourceGroup().location]", + "type": "Microsoft.Compute/virtualMachines/extensions", + "apiVersion": "2017-03-30", + "location": "[parameters('moodleCommon').location]", "name": "[concat(parameters('moodleCommon').elasticVmName2,'/','install_elastic')]", "properties": { "autoUpgradeMinorVersion": true, - "publisher": "Microsoft.OSTCExtensions", + "publisher": "Microsoft.Azure.Extensions", "settings": { - "commandToExecute": "[variables('cmdExec')]", "fileUris": [ "[variables('scriptUri')]" ] }, - "type": "CustomScriptForLinux", - "typeHandlerVersion": "1.4" + "protectedSettings":{ + "commandToExecute": "[variables('cmdExec')]" + }, + "type": "CustomScript", + "typeHandlerVersion": "2.0" }, "tags": { "displayName": "install_elastic" - }, - "type": "Microsoft.Compute/virtualMachines/extensions" + } }, { - "apiVersion": "2015-06-15", - "location": "[resourceGroup().location]", + "type": "Microsoft.Compute/virtualMachines/extensions", + "apiVersion": "2017-03-30", + "location": "[parameters('moodleCommon').location]", "name": "[concat(parameters('moodleCommon').elasticVmName3,'/','install_elastic')]", "properties": { "autoUpgradeMinorVersion": true, - "publisher": "Microsoft.OSTCExtensions", + "publisher": "Microsoft.Azure.Extensions", "settings": { - "commandToExecute": "[variables('cmdExec')]", "fileUris": [ "[variables('scriptUri')]" ] }, - "type": "CustomScriptForLinux", - "typeHandlerVersion": "1.4" + "protectedSettings":{ + "commandToExecute": "[variables('cmdExec')]" + }, + "type": "CustomScript", + "typeHandlerVersion": "2.0" }, "tags": { "displayName": "install_elastic" - }, - "type": "Microsoft.Compute/virtualMachines/extensions" + } } ], "variables": { "cmdExec": "[concat('bash ', parameters('moodleCommon').elasticScriptFilename, ' ', parameters('moodleCommon').elasticClusterName, ' ', parameters('moodleCommon').elasticVm1IP, ' ', parameters('moodleCommon').elasticVm2IP, ' ', parameters('moodleCommon').elasticVm3IP)]", "documentation01": "This sub-template applies a specific post-deployment script to the controller vm", "documentation02": "It expects certain values in the 'common' datastructure.", - "documentation03": " ScriptLocation - web URI", + "documentation03": " scriptLocation - web URI", "documentation04": " elasticScriptFilename - name of script file", "documentation05": " elasticVmName - name of the elastic search vm generic name", - "scriptUri": "[concat(parameters('moodleCommon').ScriptLocation,parameters('moodleCommon').elasticScriptFilename)]" + "scriptUri": "[concat(parameters('moodleCommon').scriptLocation,parameters('moodleCommon').elasticScriptFilename,parameters('moodleCommon').artifactsSasToken)]" } } diff --git a/nested/elastic.json b/nested/search-elastic.json similarity index 68% rename from nested/elastic.json rename to nested/search-elastic.json index ed640f21..f189d2b1 100644 --- a/nested/elastic.json +++ b/nested/search-elastic.json @@ -7,12 +7,19 @@ "description": "Common Moodle values" }, "type": "object" + }, + "subnetIdElastic": { + "metadata": { + "description": "Azure resource ID of the subnet where the Elastic Search VMs are to be deployed (if any)" + }, + "type": "string" } }, "resources": [ { - "apiVersion": "2015-06-15", - "location": "[resourceGroup().location]", + "type": "Microsoft.Network/networkInterfaces", + "apiVersion": "2017-10-01", + "location": "[parameters('moodleCommon').location]", "name": "[parameters('moodleCommon').elasticNicName1]", "properties": { "ipConfigurations": [ @@ -22,23 +29,24 @@ "privateIPAllocationMethod": "Static", "privateIPAddress": "[parameters('moodleCommon').elasticVm1IP]", "subnet": { - "id": "[variables('subnetElasticRef')]" + "id": "[parameters('subnetIdElastic')]" } } } - ] + ], + "enableAcceleratedNetworking": "[parameters('moodleCommon').enableAccelNwForOtherVmsSwitch]" }, "tags": { "displayName": "Elastic NIC 1" - }, - "type": "Microsoft.Network/networkInterfaces" + } }, { - "apiVersion": "[parameters('moodleCommon').computeApi]", + "type": "Microsoft.Compute/virtualMachines", + "apiVersion": "2017-03-30", "dependsOn": [ "[concat('Microsoft.Network/networkInterfaces/', parameters('moodleCommon').elasticNicName1)]" ], - "location": "[resourceGroup().location]", + "location": "[parameters('moodleCommon').location]", "name": "[parameters('moodleCommon').elasticVmName1]", "properties": { "hardwareProfile": { @@ -70,9 +78,9 @@ "dataDisks": [], "imageReference": "[parameters('moodleCommon').osType]", "osDisk": { - "createOption": "fromImage", + "createOption": "FromImage", "managedDisk": { - "storageAccountType": "Standard_LRS" + "storageAccountType": "[parameters('moodleCommon').osDiskStorageType]" }, "name": "[parameters('moodleCommon').elasticVmName1]" } @@ -80,11 +88,12 @@ }, "tags": { "displayName": "Elastic Search Virtual Machine" - }, - "type": "Microsoft.Compute/virtualMachines" + } }, { - "apiVersion": "2015-01-01", + "condition": "[parameters('moodleCommon').applyScriptsSwitch]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2017-05-10", "dependsOn": [ "[concat('Microsoft.Compute/virtualMachines/', parameters('moodleCommon').elasticVmName1)]" ], @@ -98,13 +107,14 @@ }, "templateLink": { - "uri": "[concat(parameters('moodleCommon').baseTemplateUrl,'elasticconfig',parameters('moodleCommon').applyScriptsSwitch,'.json')]" + "uri": "[concat(parameters('moodleCommon').baseTemplateUrl,'search-elastic-config.json',parameters('moodleCommon').artifactsSasToken)]" } - }, - "type": "Microsoft.Resources/deployments" + } }, { - "apiVersion": "2015-01-01", + "condition": "[parameters('moodleCommon').azureBackupSwitch]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2017-05-10", "dependsOn": [ "[concat('Microsoft.Compute/virtualMachines/',parameters('moodleCommon').elasticVmName1)]" ], @@ -120,14 +130,14 @@ } }, "templateLink": { - "uri": "[concat(parameters('moodleCommon').baseTemplateUrl,'recoveryservicesEnlist',parameters('moodleCommon').azureBackupSwitch,'.json')]" + "uri": "[concat(parameters('moodleCommon').baseTemplateUrl,'recoveryservicesEnlist.json',parameters('moodleCommon').artifactsSasToken)]" } - }, - "type": "Microsoft.Resources/deployments" + } }, { - "apiVersion": "2015-06-15", - "location": "[resourceGroup().location]", + "type": "Microsoft.Network/networkInterfaces", + "apiVersion": "2017-10-01", + "location": "[parameters('moodleCommon').location]", "name": "[parameters('moodleCommon').elasticNicName2]", "properties": { "ipConfigurations": [ @@ -137,23 +147,24 @@ "privateIPAllocationMethod": "Static", "privateIPAddress": "[parameters('moodleCommon').elasticVm2IP]", "subnet": { - "id": "[variables('subnetElasticRef')]" + "id": "[parameters('subnetIdElastic')]" } } } - ] + ], + "enableAcceleratedNetworking": "[parameters('moodleCommon').enableAccelNwForOtherVmsSwitch]" }, "tags": { "displayName": "Elastic NIC 2" - }, - "type": "Microsoft.Network/networkInterfaces" + } }, { - "apiVersion": "[parameters('moodleCommon').computeApi]", + "type": "Microsoft.Compute/virtualMachines", + "apiVersion": "2017-03-30", "dependsOn": [ "[concat('Microsoft.Network/networkInterfaces/', parameters('moodleCommon').elasticNicName2)]" ], - "location": "[resourceGroup().location]", + "location": "[parameters('moodleCommon').location]", "name": "[parameters('moodleCommon').elasticVmName2]", "properties": { "hardwareProfile": { @@ -167,17 +178,27 @@ ] }, "osProfile": { - "adminPassword": "[parameters('moodleCommon').sshPassword]", "adminUsername": "[parameters('moodleCommon').sshUsername]", - "computerName": "[parameters('moodleCommon').elasticVmName2]" + "computerName": "[parameters('moodleCommon').elasticVmName2]", + "linuxConfiguration": { + "disablePasswordAuthentication": true, + "ssh": { + "publicKeys": [ + { + "path": "[concat('/home/', parameters('moodleCommon').sshUsername, '/.ssh/authorized_keys')]", + "keyData": "[parameters('moodleCommon').sshPublicKey]" + } + ] + } + } }, "storageProfile": { "dataDisks": [], "imageReference": "[parameters('moodleCommon').osType]", "osDisk": { - "createOption": "fromImage", + "createOption": "FromImage", "managedDisk": { - "storageAccountType": "Standard_LRS" + "storageAccountType": "[parameters('moodleCommon').osDiskStorageType]" }, "name": "[parameters('moodleCommon').elasticVmName2]" } @@ -185,11 +206,12 @@ }, "tags": { "displayName": "Elastic Search Virtual Machine" - }, - "type": "Microsoft.Compute/virtualMachines" + } }, { - "apiVersion": "2015-01-01", + "condition": "[parameters('moodleCommon').applyScriptsSwitch]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2017-05-10", "dependsOn": [ "[concat('Microsoft.Compute/virtualMachines/', parameters('moodleCommon').elasticVmName2)]" ], @@ -203,13 +225,14 @@ }, "templateLink": { - "uri": "[concat(parameters('moodleCommon').baseTemplateUrl,'elasticconfig',parameters('moodleCommon').applyScriptsSwitch,'.json')]" + "uri": "[concat(parameters('moodleCommon').baseTemplateUrl,'search-elastic-config.json',parameters('moodleCommon').artifactsSasToken)]" } - }, - "type": "Microsoft.Resources/deployments" + } }, { - "apiVersion": "2015-01-01", + "condition": "[parameters('moodleCommon').azureBackupSwitch]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2017-05-10", "dependsOn": [ "[concat('Microsoft.Compute/virtualMachines/',parameters('moodleCommon').elasticVmName2)]" ], @@ -225,14 +248,14 @@ } }, "templateLink": { - "uri": "[concat(parameters('moodleCommon').baseTemplateUrl,'recoveryservicesEnlist',parameters('moodleCommon').azureBackupSwitch,'.json')]" + "uri": "[concat(parameters('moodleCommon').baseTemplateUrl,'recoveryservicesEnlist.json',parameters('moodleCommon').artifactsSasToken)]" } - }, - "type": "Microsoft.Resources/deployments" + } }, { - "apiVersion": "2015-06-15", - "location": "[resourceGroup().location]", + "type": "Microsoft.Network/networkInterfaces", + "apiVersion": "2017-10-01", + "location": "[parameters('moodleCommon').location]", "name": "[parameters('moodleCommon').elasticNicName3]", "properties": { "ipConfigurations": [ @@ -242,23 +265,24 @@ "privateIPAllocationMethod": "Static", "privateIPAddress": "[parameters('moodleCommon').elasticVm3IP]", "subnet": { - "id": "[variables('subnetElasticRef')]" + "id": "[parameters('subnetIdElastic')]" } } } - ] + ], + "enableAcceleratedNetworking": "[parameters('moodleCommon').enableAccelNwForOtherVmsSwitch]" }, "tags": { "displayName": "Elastic NIC 2" - }, - "type": "Microsoft.Network/networkInterfaces" + } }, { - "apiVersion": "[parameters('moodleCommon').computeApi]", + "type": "Microsoft.Compute/virtualMachines", + "apiVersion": "2017-03-30", "dependsOn": [ "[concat('Microsoft.Network/networkInterfaces/', parameters('moodleCommon').elasticNicName3)]" ], - "location": "[resourceGroup().location]", + "location": "[parameters('moodleCommon').location]", "name": "[parameters('moodleCommon').elasticVmName3]", "properties": { "hardwareProfile": { @@ -272,17 +296,27 @@ ] }, "osProfile": { - "adminPassword": "[parameters('moodleCommon').sshPassword]", "adminUsername": "[parameters('moodleCommon').sshUsername]", - "computerName": "[parameters('moodleCommon').elasticVmName3]" + "computerName": "[parameters('moodleCommon').elasticVmName3]", + "linuxConfiguration": { + "disablePasswordAuthentication": true, + "ssh": { + "publicKeys": [ + { + "path": "[concat('/home/', parameters('moodleCommon').sshUsername, '/.ssh/authorized_keys')]", + "keyData": "[parameters('moodleCommon').sshPublicKey]" + } + ] + } + } }, "storageProfile": { "dataDisks": [], "imageReference": "[parameters('moodleCommon').osType]", "osDisk": { - "createOption": "fromImage", + "createOption": "FromImage", "managedDisk": { - "storageAccountType": "Standard_LRS" + "storageAccountType": "[parameters('moodleCommon').osDiskStorageType]" }, "name": "[parameters('moodleCommon').elasticVmName3]" } @@ -290,11 +324,12 @@ }, "tags": { "displayName": "Elastic Search Virtual Machine" - }, - "type": "Microsoft.Compute/virtualMachines" + } }, { - "apiVersion": "2015-01-01", + "condition": "[parameters('moodleCommon').applyScriptsSwitch]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2017-05-10", "dependsOn": [ "[concat('Microsoft.Compute/virtualMachines/', parameters('moodleCommon').elasticVmName3)]" ], @@ -308,13 +343,14 @@ }, "templateLink": { - "uri": "[concat(parameters('moodleCommon').baseTemplateUrl,'elasticconfig',parameters('moodleCommon').applyScriptsSwitch,'.json')]" + "uri": "[concat(parameters('moodleCommon').baseTemplateUrl,'search-elastic-config.json',parameters('moodleCommon').artifactsSasToken)]" } - }, - "type": "Microsoft.Resources/deployments" + } }, { - "apiVersion": "2015-01-01", + "condition": "[parameters('moodleCommon').azureBackupSwitch]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2017-05-10", "dependsOn": [ "[concat('Microsoft.Compute/virtualMachines/',parameters('moodleCommon').elasticVmName3)]" ], @@ -330,10 +366,9 @@ } }, "templateLink": { - "uri": "[concat(parameters('moodleCommon').baseTemplateUrl,'recoveryservicesEnlist',parameters('moodleCommon').azureBackupSwitch,'.json')]" + "uri": "[concat(parameters('moodleCommon').baseTemplateUrl,'recoveryservicesEnlist.json',parameters('moodleCommon').artifactsSasToken)]" } - }, - "type": "Microsoft.Resources/deployments" + } } ], "variables": { @@ -341,22 +376,20 @@ "documentation02": "It expects certain values in the 'common' datastructure.", "documentation03": " vnetName - name of the virtual network", "documentation04": " subnetElastic - name of subnet for elastic (and vm scale set)", - "documentation05": " computeApi - the 'managed' resources need to all use this value", "documentation06": " elasticNicName1 - name of the eastlic vm 1 network interface", "documentation07": " elasticNicName2 - name of the eastlic vm 2 network interface", "documentation08": " elasticNicName3 - name of the eastlic vm 3 network interface", "documentation09": " elasticVmName1 - name of the eastlic vm 1", "documentation10": " elasticVmName2 - name of the eastlic vm 2", "documentation11": " elasticVmName3 - name of the eastlic vm 3", - "documentation09": " elasticVm1IP - IP of the eastlic vm 1", - "documentation10": " elasticVm2IP - IP of the eastlic vm 2", - "documentation11": " elasticVm3IP - IP of the eastlic vm 3", - "documentation12": "This sub-template calls other sub-templates", - "documentation13": " elasticconfig - conditionally applies post-deployment script on the VM", - "documentation14": " recoveryservicesEnlist - conditionally enlists the VM into the backup regimen", + "documentation12": " elasticVm1IP - IP of the eastlic vm 1", + "documentation13": " elasticVm2IP - IP of the eastlic vm 2", + "documentation14": " elasticVm3IP - IP of the eastlic vm 3", + "documentation15": "This sub-template calls other sub-templates", + "documentation16": " elasticconfig - conditionally applies post-deployment script on the VM", + "documentation17": " recoveryservicesEnlist - conditionally enlists the VM into the backup regimen", "nicRef1": "[resourceId('Microsoft.Network/networkInterfaces', parameters('moodleCommon').elasticNicName1)]", "nicRef2": "[resourceId('Microsoft.Network/networkInterfaces', parameters('moodleCommon').elasticNicName2)]", - "nicRef3": "[resourceId('Microsoft.Network/networkInterfaces', parameters('moodleCommon').elasticNicName3)]", - "subnetElasticRef": "[concat(resourceId('Microsoft.Network/virtualNetworks', parameters('moodleCommon').vnetName), '/subnets/',parameters('moodleCommon').subnetElastic)]" + "nicRef3": "[resourceId('Microsoft.Network/networkInterfaces', parameters('moodleCommon').elasticNicName3)]" } } diff --git a/nested/storageAccount.json b/nested/storageAccount.json new file mode 100644 index 00000000..bbdbafe8 --- /dev/null +++ b/nested/storageAccount.json @@ -0,0 +1,96 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "moodleCommon": { + "metadata": { + "description": "Common Moodle values" + }, + "type": "object" + } + }, + "resources": [ + { + "condition": "[not(equals(parameters('moodleCommon').fileServerType, 'azurefiles'))]", + "type": "Microsoft.Storage/storageAccounts", + "apiVersion": "2019-06-01", + "location": "[parameters('moodleCommon').location]", + "name": "[concat(parameters('moodleCommon').storageAccountName,'naf')]", + "kind": "Storage", + "sku": { + "name": "[parameters('moodleCommon').storageAccountType]" + }, + "properties": { + "encryption": { + "keySource": "Microsoft.Storage", + "services": { + "blob": { + "enabled": true + }, + "file": { + "enabled": true + } + } + }, + "networkAcls": { + "bypass": "AzureServices", + "defaultAction": "Allow", + "ipRules": [], + "virtualNetworkRules": [] + }, + "supportsHttpsTrafficOnly": true + } + }, + { + "condition": "[equals(parameters('moodleCommon').fileServerType, 'azurefiles')]", + "type": "Microsoft.Storage/storageAccounts", + "apiVersion": "2019-06-01", + "location": "[parameters('moodleCommon').location]", + "name": "[concat(parameters('moodleCommon').storageAccountName,'af')]", + "kind": "[if(equals(parameters('moodleCommon').storageAccountType, 'Premium_LRS'), 'FileStorage', 'Storage')]", + "sku": { + "name": "[parameters('moodleCommon').storageAccountType]" + }, + "properties": { + "largeFileSharesState": "Enabled", + "networkAcls": { + "bypass": "AzureServices", + "virtualNetworkRules": [], + "ipRules": [], + "defaultAction": "Allow" + }, + "supportsHttpsTrafficOnly": true, + "encryption": { + "services": { + "file": { + "keyType": "Account", + "enabled": true + }, + "blob": { + "keyType": "Account", + "enabled": true + } + }, + "keySource": "Microsoft.Storage" + } + } + } + ], + "variables": { + "documentation1": "This sub-template creates a storage account. It expects certain values in the 'common' datastructure.", + "documentation2": " storageAccountName - name of storage account", + "documentation3": " storageAccountType - type of storage account", + "storageName": "[concat(parameters('moodleCommon').storageAccountName,if(equals(parameters('moodleCommon').fileServerType, 'azurefiles'), 'af', 'naf'))]", + "storageAccountId": "[resourceId('Microsoft.Storage/storageAccounts', variables('storageName'))]" + }, + "outputs": { + "storageAccountKey": { + "value": "[listKeys(variables('storageAccountId'), '2019-06-01').keys[0].value]", + "type": "string" + }, + "storageAccountName": { + "value": "[variables('storageName')]", + "type": "string" + } + } +} diff --git a/nested/tika.json b/nested/tika.json new file mode 100644 index 00000000..25c0f257 --- /dev/null +++ b/nested/tika.json @@ -0,0 +1,151 @@ +{ + "$schema": "http://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "moodleCommon": { + "metadata": { + "description": "Common Moodle values" + }, + "type": "object" + }, + "subnetIdTika": { + "metadata": { + "description": "Azure resource ID of the subnet where this Tika VM is to be deployed" + }, + "type": "string" + } + }, + "resources": [ + { + "type": "Microsoft.Network/networkInterfaces", + "apiVersion": "2017-10-01", + "location": "[parameters('moodleCommon').location]", + "name": "[parameters('moodleCommon').tikaNicName]", + "properties": { + "ipConfigurations": [ + { + "name": "ipcfg-tika", + "properties": { + "privateIPAllocationMethod": "Static", + "privateIPAddress": "[parameters('moodleCommon').tikaVmIP]", + "subnet": { + "id": "[parameters('subnetIdTika')]" + } + } + } + ], + "enableAcceleratedNetworking": "[parameters('moodleCommon').enableAccelNwForOtherVmsSwitch]" + }, + "tags": { + "displayName": "Tika NIC" + } + }, + { + "type": "Microsoft.Compute/virtualMachines", + "apiVersion": "2017-03-30", + "dependsOn": [ + "[concat('Microsoft.Network/networkInterfaces/', parameters('moodleCommon').tikaNicName)]" + ], + "location": "[parameters('moodleCommon').location]", + "name": "[parameters('moodleCommon').tikaVmName]", + "properties": { + "hardwareProfile": { + "vmSize": "[parameters('moodleCommon').tikaVmSku]" + }, + "networkProfile": { + "networkInterfaces": [ + { + "id": "[variables('nicRef')]" + } + ] + }, + "osProfile": { + "adminUsername": "[parameters('moodleCommon').sshUsername]", + "computerName": "[parameters('moodleCommon').tikaVmName]", + "linuxConfiguration": { + "disablePasswordAuthentication": true, + "ssh": { + "publicKeys": [ + { + "path": "[concat('/home/', parameters('moodleCommon').sshUsername, '/.ssh/authorized_keys')]", + "keyData": "[parameters('moodleCommon').sshPublicKey]" + } + ] + } + } + }, + "storageProfile": { + "dataDisks": [], + "imageReference": "[parameters('moodleCommon').osType]", + "osDisk": { + "createOption": "FromImage", + "managedDisk": { + "storageAccountType": "[parameters('moodleCommon').osDiskStorageType]" + }, + "name": "[parameters('moodleCommon').tikaVmName]" + } + } + }, + "tags": { + "displayName": "Tika Service Virtual Machine" + } + }, + { + "condition": "[parameters('moodleCommon').applyScriptsSwitch]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2017-05-10", + "dependsOn": [ + "[concat('Microsoft.Compute/virtualMachines/', parameters('moodleCommon').tikaVmName)]" + ], + "name": "[concat(parameters('moodleCommon').tikaVmName,'-ScriptProcessor')]", + "properties": { + "mode": "Incremental", + "parameters": { + "moodleCommon": { + "value": "[parameters('moodleCommon')]" + } + + }, + "templateLink": { + "uri": "[concat(parameters('moodleCommon').baseTemplateUrl,'tikaconfig.json',parameters('moodleCommon').artifactsSasToken)]" + } + } + }, + { + "condition": "[parameters('moodleCommon').azureBackupSwitch]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2017-05-10", + "dependsOn": [ + "[concat('Microsoft.Compute/virtualMachines/',parameters('moodleCommon').tikaVmName)]" + ], + "name": "[concat(parameters('moodleCommon').tikaVmName,'-Backup')]", + "properties": { + "mode": "Incremental", + "parameters": { + "moodleCommon": { + "value": "[parameters('moodleCommon')]" + }, + "vmName": { + "value": "[parameters('moodleCommon').tikaVmName]" + } + }, + "templateLink": { + "uri": "[concat(parameters('moodleCommon').baseTemplateUrl,'recoveryservicesEnlist.json',parameters('moodleCommon').artifactsSasToken)]" + } + } + } + ], + "variables": { + "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", + "documentation02": "It expects certain values in the 'common' datastructure.", + "documentation03": " vnetName - name of the virtual network", + "documentation04": " subnetTika - name of subnet for tika (and vm scale set)", + "documentation06": " tikaNicName - name of the tika vm network interface", + "documentation11": " tikaVmName - name of the tika vm", + "documentation12": " tikaVmIP - IP of the tika vm", + "documentation15": "This sub-template calls other sub-templates", + "documentation16": " tikaconfig - conditionally applies post-deployment script on the VM", + "documentation17": " recoveryservicesEnlist - conditionally enlists the VM into the backup regimen", + "nicRef": "[resourceId('Microsoft.Network/networkInterfaces', parameters('moodleCommon').tikaNicName)]" + } +} diff --git a/nested/tikaconfig.json b/nested/tikaconfig.json new file mode 100644 index 00000000..b0868a56 --- /dev/null +++ b/nested/tikaconfig.json @@ -0,0 +1,46 @@ +{ + "$schema": "http://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "moodleCommon": { + "metadata": { + "description": "Common Moodle values" + }, + "type": "object" + } + }, + "resources": [ + { + "type": "Microsoft.Compute/virtualMachines/extensions", + "apiVersion": "2017-03-30", + "location": "[parameters('moodleCommon').location]", + "name": "[concat(parameters('moodleCommon').tikaVmName,'/','install_tika')]", + "properties": { + "autoUpgradeMinorVersion": true, + "publisher": "Microsoft.Azure.Extensions", + "settings": { + "fileUris": [ + "[variables('scriptUri')]" + ] + }, + "protectedSettings":{ + "commandToExecute": "[variables('cmdExec')]" + }, + "type": "CustomScript", + "typeHandlerVersion": "2.0" + }, + "tags": { + "displayName": "install_tika" + } + } + ], + "variables": { + "cmdExec": "[concat('bash ', parameters('moodleCommon').tikaScriptFilename, ' ', parameters('moodleCommon').tikaVmIP)]", + "documentation01": "This sub-template applies a specific post-deployment script to the tika vm", + "documentation02": "It expects certain values in the 'common' datastructure.", + "documentation03": " scriptLocation - web URI", + "documentation04": " tikaScriptFilename - name of script file", + "documentation05": " tikaVmName - name of the tika search vm generic name", + "scriptUri": "[concat(parameters('moodleCommon').scriptLocation,parameters('moodleCommon').tikaScriptFilename,parameters('moodleCommon').artifactsSasToken)]" + } +} diff --git a/nested/vmNameListSelecter.json b/nested/vmNameListSelecter.json deleted file mode 100644 index 53b9d17a..00000000 --- a/nested/vmNameListSelecter.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "outputs": { - "vmNameArray": { - "type": "string", - "value": "[string(take(variables('diskArray'),parameters('vmCount')))]" - } - }, - "parameters": { - "vmCount": { - "maxValue": 8, - "minValue": 2, - "type": "int" - }, - "vmNameRoot": { - "type": "string" - } - }, - "resources": [], - "variables": { - "diskArray": [ - "[concat(parameters('vmNameRoot'),'0')]", - "[concat(parameters('vmNameRoot'),'1')]", - "[concat(parameters('vmNameRoot'),'2')]", - "[concat(parameters('vmNameRoot'),'3')]", - "[concat(parameters('vmNameRoot'),'4')]", - "[concat(parameters('vmNameRoot'),'5')]", - "[concat(parameters('vmNameRoot'),'6')]", - "[concat(parameters('vmNameRoot'),'7')]" - ], - "documentation01": "This sub-template provides a way to return a flexible number of vmNames", - "documentation02": " vmNameRoot - used as apart of the naming of the VM(s)", - "documentation03": " vmCount - used to limit the nubmer of names returned (via the TAKE function) " - } -} diff --git a/nested/vmsetupparams.json b/nested/vmsetupparams.json new file mode 100644 index 00000000..26f92402 --- /dev/null +++ b/nested/vmsetupparams.json @@ -0,0 +1,107 @@ +{ + "$schema": "http://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "moodleCommon": { + "metadata": { + "description": "Common Moodle values" + }, + "type": "object" + }, + "dbFQDN": { + "metadata": { + "description": "FQDN of the deployed SQL DB" + }, + "type": "string" + }, + "storageAccountName": { + "metadata": { + "description": "Storage account name from the storage account deployment." + }, + "type": "string" + }, + "storageAccountKey": { + "metadata": { + "description": "Storage account key from the storage account deployment." + }, + "type": "string" + }, + "redisKey": { + "metadata": { + "description": "Redis cache key from the redis deployment ('None' if redis is not selected). This just gets passed through to the controllersetup.json." + }, + "type": "string" + }, + "azureSearchKey": { + "metadata": { + "description": "Azure Search primary key from the Azure Search service deployment ('None' if Azure Search service is not selected)" + }, + "type": "string" + } + }, + "resources": [], + "variables": { + "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", + "vmSetupParamsObj": { + "siteProfile": { + "siteURL": "[parameters('moodleCommon').siteURL]", + "httpsTermination": "[parameters('moodleCommon').httpsTermination]", + "thumbprintSslCert": "[parameters('moodleCommon').thumbprintSslCert]", + "thumbprintCaCert": "[parameters('moodleCommon').thumbprintCaCert]" + }, + "moodleProfile": { + "version": "[parameters('moodleCommon').moodleVersion]", + "dbName": "[parameters('moodleCommon').moodleDbName]", + "dbUser": "[parameters('moodleCommon').moodleDbUser]", + "dbUserAzure": "[parameters('moodleCommon').moodleDbUserAzure]", + "dbPassword": "[parameters('moodleCommon').moodleDbPass]", + "adminPassword": "[parameters('moodleCommon').moodleAdminPass]", + "storageAccountName": "[parameters('storageAccountName')]", + "storageAccountKey": "[parameters('storageAccountKey')]", + "storageAccountType": "[parameters('moodleCommon').storageAccountType]", + "redisDns": "[parameters('moodleCommon').redisDns]", + "redisKey": "[parameters('redisKey')]", + "elasticVm1IP": "[parameters('moodleCommon').elasticVm1IP]", + "installO365pluginsSwitch": "[parameters('moodleCommon').installO365pluginsSwitch]", + "installObjectFsSwitch": "[parameters('moodleCommon').installObjectFsSwitch]", + "installGdprPluginsSwitch": "[parameters('moodleCommon').installGdprPluginsSwitch]", + "searchType": "[parameters('moodleCommon').searchType]", + "azureSearchKey": "[parameters('azureSearchKey')]", + "azureSearchNameHost": "[parameters('moodleCommon').azureSearchNameHost]", + "tikaVmIP": "[parameters('moodleCommon').tikaVmIP]", + "syslogServer": "[parameters('moodleCommon').ctlrVmName]", + "webServerType": "[parameters('moodleCommon').webServerType]", + "htmlLocalCopySwitch": "[parameters('moodleCommon').htmlLocalCopySwitch]" + }, + "dbServerProfile": { + "type": "[parameters('moodleCommon').dbServerType]", + "fqdn": "[parameters('dbFQDN')]", + "adminLogin": "[parameters('moodleCommon').dbLogin]", + "adminLoginAzure": "[concat(parameters('moodleCommon').dbLogin, '@', parameters('moodleCommon').dbServerType, '-', parameters('moodleCommon').resourcesPrefix)]", + "adminPassword": "[parameters('moodleCommon').dbLoginPassword]", + "mssqlDbServiceObjectiveName": "[parameters('moodleCommon').mssqlDbServiceObjectiveName]", + "mssqlDbEdition": "[parameters('moodleCommon').mssqlDbEdition]", + "mssqlDbSize": "[parameters('moodleCommon').mssqlDbSize]" + }, + "fileServerProfile": { + "type": "[parameters('moodleCommon').fileServerType]", + "nfsVmName": "[parameters('moodleCommon').ctlrVmName]", + "glusterVmName": "[concat(parameters('moodleCommon').gfsNameRoot, '0')]", + "glusterVolName": "data", + "nfsByoIpExportPath": "[parameters('moodleCommon').nfsByoIpExportPath]", + "nfsHaLbIP": "[parameters('moodleCommon').nfsHaLbIP]", + "nfsHaExportPath": "[parameters('moodleCommon').nfsHaExportPath]", + "fileServerDiskSize": "[parameters('moodleCommon').fileServerDiskSize]" + }, + "phpProfile": { + "phpVersion": "[parameters('moodleCommon').phpVersion]" + } + } + }, + "outputs": { + "vmSetupParamsObj": { + "value": "[variables('vmSetupParamsObj')]", + "type": "object" + } + } +} diff --git a/nested/webvmss.json b/nested/webvmss.json index c792ee10..187bc353 100644 --- a/nested/webvmss.json +++ b/nested/webvmss.json @@ -1,5 +1,5 @@ { - "$schema": "http://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", "contentVersion": "1.0.0.0", "parameters": { "moodleCommon": { @@ -7,64 +7,103 @@ "description": "Common Moodle values" }, "type": "object" + }, + "subnetIdWeb": { + "metadata": { + "description": "Azure resource ID of the subnet where this VMSS is to be deployed" + }, + "type": "string" + }, + "vmSetupParamsObj": { + "metadata": { + "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)" + }, + "type": "object" } }, "resources": [ - { - "apiVersion": "[variables('storageApiVersion')]", - "location": "[resourceGroup().location]", - "name": "[parameters('moodleCommon').vmssdStorageAccounttName]", + { + "type": "Microsoft.Network/networkSecurityGroups", + "apiVersion": "2017-10-01", + "location": "[parameters('moodleCommon').location]", + "name": "[parameters('moodleCommon').vmssNsgName]", "properties": { - "accountType": "Standard_LRS" + "securityRules": [ + { + "name": "Allow_http", + "properties": { + "access": "Allow", + "destinationAddressPrefix": "*", + "destinationPortRange": "80", + "direction": "Inbound", + "priority": 1000, + "protocol": "Tcp", + "sourceAddressPrefix": "*", + "sourcePortRange": "*" + } + }, + { + "name": "Allow_https", + "properties": { + "access": "Allow", + "destinationAddressPrefix": "*", + "destinationPortRange": "443", + "direction": "Inbound", + "priority": 1005, + "protocol": "Tcp", + "sourceAddressPrefix": "*", + "sourcePortRange": "*" + } + } + ] }, - "type": "Microsoft.Storage/storageAccounts" + "tags": { + "displayName": "VMSS NSG" + } }, { - "apiVersion": "2016-04-30-preview", + "type": "Microsoft.Storage/storageAccounts", + "apiVersion": "2019-06-01", + "location": "[parameters('moodleCommon').location]", + "name": "[parameters('moodleCommon').vmssdStorageAccounttName]", + "kind": "Storage", + "sku": { + "name": "Standard_LRS" + } + }, + { + "type": "Microsoft.Compute/virtualMachineScaleSets", + "apiVersion": "2019-07-01", "dependsOn": [ - "[concat('Microsoft.Storage/storageAccounts/', parameters('moodleCommon').vmssdStorageAccounttName)]" + "[concat('Microsoft.Storage/storageAccounts/', parameters('moodleCommon').vmssdStorageAccounttName)]", + "[concat('Microsoft.Network/networkSecurityGroups/', parameters('moodleCommon').vmssNsgName)]" ], - "location": "[resourceGroup().location]", + "location": "[parameters('moodleCommon').location]", "name": "[parameters('moodleCommon').vmssName]", "properties": { - "overprovision": "true", + "overprovision": true, "upgradePolicy": { "mode": "Manual" }, "virtualMachineProfile": { "extensionProfile": { "extensions": [ - { - "name": "LinuxDiagnostic", - "properties": { - "autoUpgradeMinorVersion": true, - "protectedSettings": { - "storageAccountEndPoint": "https://core.windows.net", - "storageAccountKey": "[listkeys(variables('dstorID'), variables('storageApiVersion')).key1]", - "storageAccountName": "[parameters('moodleCommon').vmssdStorageAccounttName]" - }, - "publisher": "Microsoft.OSTCExtensions", - "settings": { - "storageAccount": "[parameters('moodleCommon').vmssdStorageAccounttName]", - "xmlCfg": "[base64(concat(variables('wadCfgxStart'), variables('vmssID'), variables('wadCfgxEnd')))]" - }, - "type": "LinuxDiagnostic", - "typeHandlerVersion": "2.3" - } - }, { "name": "setup_moodle", "properties": { "autoUpgradeMinorVersion": true, - "publisher": "Microsoft.OSTCExtensions", + "publisher": "Microsoft.Azure.Extensions", "settings": { - "commandToExecute": "[variables('cmdExec')]", "fileUris": [ - "[variables('scriptUri')]" + "[variables('scriptUri')]", + "[parameters('moodleCommon').commonFunctionsScriptUri]" ] }, - "type": "CustomScriptForLinux", - "typeHandlerVersion": "1.4" + "protectedSettings": { + "commandToExecute": "[concat('bash ', parameters('moodleCommon').webServerSetupScriptFilename, ' ', parameters('moodleCommon').moodleOnAzureConfigsJsonPath)]" + }, + "type": "CustomScript", + "typeHandlerVersion": "2.0" } } ] @@ -78,18 +117,19 @@ { "name": "ipcfg_lb", "properties": { - "loadBalancerBackendAddressPools": [ - { - "id": "[variables('extBeID')]" - } - ], + "loadBalancerBackendAddressPools": "[take(variables('lbBePoolArray'), variables('lbBePoolArrayTakeCount'))]", + "applicationGatewayBackendAddressPools": "[take(variables('appGwBePoolArray'), variables('appGwBePoolArrayTakeCount'))]", "subnet": { - "id": "[variables('subnetWebRef')]" + "id": "[parameters('subnetIdWeb')]" } } } ], - "primary": "true" + "primary": true, + "enableAcceleratedNetworking": "[parameters('moodleCommon').enableAccelNwForOtherVmsSwitch]", + "networkSecurityGroup": { + "id": "[ variables('vmssNsgNameId') ]" + } } } ] @@ -97,6 +137,7 @@ "osProfile": { "adminUsername": "[parameters('moodleCommon').sshUsername]", "computerNamePrefix": "[parameters('moodleCommon').vmssName]", + "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')))]", "linuxConfiguration": { "disablePasswordAuthentication": true, "ssh": { @@ -114,29 +155,30 @@ "osDisk": { "caching": "ReadOnly", "createOption": "FromImage", + "diskSizeGB": 256, "managedDisk": { - "storageAccountType": "Standard_LRS" + "storageAccountType": "[parameters('moodleCommon').osDiskStorageType]" } } } } }, "sku": { - "capacity": 2, + "capacity": 1, "name": "[parameters('moodleCommon').autoscaleVmSku]", "tier": "Standard" }, "tags": { "displayName": "webfarm" - }, - "type": "Microsoft.Compute/virtualMachineScaleSets" + } }, { + "type": "Microsoft.Insights/autoscaleSettings", "apiVersion": "2015-04-01", "dependsOn": [ "[concat('Microsoft.Compute/virtualMachineScaleSets/', parameters('moodleCommon').vmssName)]" ], - "location": "[resourceGroup().location]", + "location": "[parameters('moodleCommon').location]", "name": "autoscalewad", "properties": { "enabled": true, @@ -144,26 +186,26 @@ "profiles": [ { "capacity": { - "default": "2", - "maximum": "[parameters('moodleCommon').autoscaleVmCount]", - "minimum": "2" + "default": "[parameters('moodleCommon').autoscaleVmCountMin]", + "maximum": "[parameters('moodleCommon').autoscaleVmCountMax]", + "minimum": "[parameters('moodleCommon').autoscaleVmCountMin]" }, "name": "Profile1", "rules": [ { "metricTrigger": { - "metricName": "\\Processor\\PercentProcessorTime", + "metricName": "Percentage CPU", "metricNamespace": "", "metricResourceUri": "[variables('vmssID')]", "operator": "GreaterThan", "statistic": "Average", - "threshold": 80, + "threshold": 60, "timeAggregation": "Average", "timeGrain": "PT1M", "timeWindow": "PT5M" }, "scaleAction": { - "cooldown": "PT1M", + "cooldown": "PT10M", "direction": "Increase", "type": "ChangeCount", "value": "1" @@ -171,18 +213,18 @@ }, { "metricTrigger": { - "metricName": "\\Processor\\PercentProcessorTime", + "metricName": "Percentage CPU", "metricNamespace": "", "metricResourceUri": "[variables('vmssID')]", "operator": "LessThan", "statistic": "Average", - "threshold": 50, + "threshold": 30, "timeAggregation": "Average", "timeGrain": "PT1M", "timeWindow": "PT5M" }, "scaleAction": { - "cooldown": "PT5M", + "cooldown": "PT10M", "direction": "Decrease", "type": "ChangeCount", "value": "1" @@ -192,27 +234,34 @@ } ], "targetResourceUri": "[variables('vmssID')]" - }, - "type": "Microsoft.Insights/autoscaleSettings" + } } ], "variables": { - "cmdExec": "[concat('bash ',parameters('moodleCommon').moodleSetupScriptFilename,' ',parameters('moodleCommon').gfsNameRoot,'0', ' ','data', ' ', parameters('moodleCommon').siteURL, ' ', concat('jumpbox-vm-',parameters('moodleCommon').resourcesPrefix))]", + "singleQuote": "'", "dstorID": "[resourceId('Microsoft.Storage/storageAccounts',parameters('moodleCommon').vmssdStorageAccounttName)]", "extBeID": "[concat(variables('extLbID'),'/backendAddressPools/',parameters('moodleCommon').extBeName)]", "extFeID": "[concat(variables('extLbID'),'/frontendIPConfigurations/',parameters('moodleCommon').extFeName)]", "extLbID": "[resourceId('Microsoft.Network/loadBalancers',parameters('moodleCommon').lbName)]", - "extProbeID": "[concat(variables('extLbID'),'/probes/',parameters('moodleCommon').extProbe )]", "pipID": "[resourceId('Microsoft.Network/publicIPAddresses',parameters('moodleCommon').lbPipName)]", - "scriptUri": "[concat(parameters('moodleCommon').ScriptLocation,parameters('moodleCommon').moodleSetupScriptFilename)]", - "storageApiVersion": "2015-06-15", - "subnetWebRef": "[concat(resourceId('Microsoft.Network/virtualNetworks',parameters('moodleCommon').vnetName),'/subnets/',parameters('moodleCommon').subnetWeb)]", + "scriptUri": "[concat(parameters('moodleCommon').scriptLocation,parameters('moodleCommon').webServerSetupScriptFilename,parameters('moodleCommon').artifactsSasToken)]", "vmssID": "[resourceId('Microsoft.Compute/virtualMachineScaleSets',parameters('moodleCommon').vmssName)]", - "wadCfgxEnd": "[concat('\">')]", - "wadCfgxStart": "[concat(variables('wadLogs'),variables('wadPerfCounter'),'", - "wadPerfCounter": "", - "webvmss1NIC": "[concat('Microsoft.Compute/virtualMachineScaleSets/', parameters('moodleCommon').vmssName, '/virtualMachines/0/networkInterfaces/vmssnic')]" + "webvmss1NIC": "[concat('Microsoft.Compute/virtualMachineScaleSets/', parameters('moodleCommon').vmssName, '/virtualMachines/0/networkInterfaces/vmssnic')]", + "appGwID": "[resourceId('Microsoft.Network/applicationGateways', parameters('moodleCommon').appGwName)]", + "appGwBePoolId": "[concat(variables('appGwID'), '/backendAddressPools/', parameters('moodleCommon').appGwBePoolName)]", + "vmssNsgNameId": "[resourceId('Microsoft.Network/networkSecurityGroups', parameters('moodleCommon').vmssNsgName)]", + "lbBePoolArray": [ + { + "id": "[variables('extBeID')]" + } + ], + "lbBePoolArrayTakeCount": "[if(equals(parameters('moodleCommon').httpsTermination, 'AppGw'), 0, 1)]", + "appGwBePoolArray": [ + { + "id": "[variables('appGwBePoolId')]" + } + ], + "appGwBePoolArrayTakeCount": "[if(equals(parameters('moodleCommon').httpsTermination, 'AppGw'), 1, 0)]" }, "outputs": { "webvm1IP": { diff --git a/nested/webvmssconfig0.json b/nested/webvmssconfig0.json deleted file mode 100644 index 4b9fcc89..00000000 --- a/nested/webvmssconfig0.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "$schema": "http://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "parameters": { - "moodleCommon": { - "metadata": { - "description": "Common Moodle values" - }, - "type": "object" - } - }, - "resources": [], - "variables": { - "documentation1": "This sub-template intentionally blank - a blank template allows for an optional exectuion strategy", - "documentation2": "", - "documentation3": "You should look for a template of the same name, but with a 1 instead of a 0 (at the end)" - } -} diff --git a/nested/webvmssconfig1.json b/nested/webvmssconfig1.json deleted file mode 100644 index 9cf91e03..00000000 --- a/nested/webvmssconfig1.json +++ /dev/null @@ -1,40 +0,0 @@ -{ - "$schema": "http://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "parameters": { - "moodleCommon": { - "metadata": { - "description": "Common Moodle values" - }, - "type": "object" - } - }, - "resources": [ - { - "apiVersion": "2015-06-15", - "location": "[resourceGroup().location]", - "name": "[concat(parameters('moodleCommon').vmssName,'/','setup_moodle')]", - "properties": { - "autoUpgradeMinorVersion": true, - "publisher": "Microsoft.OSTCExtensions", - "settings": { - "commandToExecute": "[variables('cmdExec')]", - "fileUris": [ - "[variables('scriptUri')]" - ] - }, - "type": "CustomScriptForLinux", - "typeHandlerVersion": "1.4" - }, - "tags": { - "displayName": "setup_moodle" - }, - "type": "Microsoft.Compute/virtualMachines/extensions" - } - - ], - "variables": { - "cmdExec": "[concat('bash ',parameters('moodleCommon').moodleSetupScriptFilename,' ',parameters('moodleCommon').gfsNameRoot,'0', ' ','data', ' ', parameters('moodleCommon').siteURL, ' ', concat('jumpbox-vm-',parameters('moodleCommon').resourcesPrefix))]", - "scriptUri": "[concat(parameters('moodleCommon').ScriptLocation,parameters('moodleCommon').moodleSetupScriptFilename)]" - } -} diff --git a/package.json b/package.json new file mode 100644 index 00000000..5705382c --- /dev/null +++ b/package.json @@ -0,0 +1,23 @@ +{ + "name": "azure-moodle", + "version": "1.0.0", + "description": "A package file for developer depenedencies when testing templates", + "scripts": { + "test": "grunt test" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/Azure/Moodle.git" + }, + "author": "Catalyst IT", + "license": "GPL V3", + "bugs": { + "url": "https://github.com/Azure/Moodle/issues" + }, + "homepage": "https://github.com/Azure/Moodle#readme", + "devDependencies": { + "grunt": "^0.4.5", + "grunt-contrib-jshint": "^0.11.3", + "load-grunt-tasks": "^3.3.0" + } +} diff --git a/scripts/mysql_install_moodle.sh b/scripts/helper_functions.sh similarity index 55% rename from scripts/mysql_install_moodle.sh rename to scripts/helper_functions.sh index c49dd026..0f1da129 100644 --- a/scripts/mysql_install_moodle.sh +++ b/scripts/helper_functions.sh @@ -1,1144 +1,453 @@ #!/bin/bash -# The MIT License (MIT) -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in all -# copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -# SOFTWARE. - -#parameters -{ - moodleVersion=${1} - glusterNode=${2} - glusterVolume=${3} - siteFQDN=${4} - mysqlIP=${5} - moodledbname=${6} - moodledbuser=${7} - moodledbpass=${8} - adminpass=${9} - mysqladminlogin=${10} - mysqladminpass=${11} - wabsacctname=${12} - wabsacctkey=${13} - azuremoodledbuser=${14} - redisDns=${15} - redisAuth=${16} - elasticVm1IP=${17} - - echo $moodleVersion >> /tmp/vars.txt - echo $glusterNode >> /tmp/vars.txt - echo $glusterVolume >> /tmp/vars.txt - echo $siteFQDN >> /tmp/vars.txt - echo $mysqlIP >> /tmp/vars.txt - echo $moodledbname >> /tmp/vars.txt - echo $moodledbuser >> /tmp/vars.txt - echo $moodledbpass >> /tmp/vars.txt - echo $adminpass >> /tmp/vars.txt - echo $mysqladminlogin >> /tmp/vars.txt - echo $mysqladminpass >> /tmp/vars.txt - echo $wabsacctname >> /tmp/vars.txt - echo $wabsacctkey >> /tmp/vars.txt - echo $azuremoodledbuser >> /tmp/vars.txt - echo $redisDns >> /tmp/vars.txt - echo $redisAuth >> /tmp/vars.txt - echo $elasticVm1IP >> /tmp/vars.txt - - # make sure system does automatic updates and fail2ban - sudo apt-get -y update - sudo apt-get -y install unattended-upgrades fail2ban - - # configure fail2ban - cat < /etc/fail2ban/jail.conf -# Fail2Ban configuration file. -# -# This file was composed for Debian systems from the original one -# provided now under /usr/share/doc/fail2ban/examples/jail.conf -# for additional examples. -# -# Comments: use '#' for comment lines and ';' for inline comments -# -# To avoid merges during upgrades DO NOT MODIFY THIS FILE -# and rather provide your changes in /etc/fail2ban/jail.local -# - -# The DEFAULT allows a global definition of the options. They can be overridden -# in each jail afterwards. - -[DEFAULT] - -# "ignoreip" can be an IP address, a CIDR mask or a DNS host. Fail2ban will not -# ban a host which matches an address in this list. Several addresses can be -# defined using space separator. -ignoreip = 127.0.0.1/8 - -# "bantime" is the number of seconds that a host is banned. -bantime = 600 - -# A host is banned if it has generated "maxretry" during the last "findtime" -# seconds. -findtime = 600 -maxretry = 3 - -# "backend" specifies the backend used to get files modification. -# Available options are "pyinotify", "gamin", "polling" and "auto". -# This option can be overridden in each jail as well. -# -# pyinotify: requires pyinotify (a file alteration monitor) to be installed. -# If pyinotify is not installed, Fail2ban will use auto. -# gamin: requires Gamin (a file alteration monitor) to be installed. -# If Gamin is not installed, Fail2ban will use auto. -# polling: uses a polling algorithm which does not require external libraries. -# auto: will try to use the following backends, in order: -# pyinotify, gamin, polling. -backend = auto - -# "usedns" specifies if jails should trust hostnames in logs, -# warn when reverse DNS lookups are performed, or ignore all hostnames in logs -# -# yes: if a hostname is encountered, a reverse DNS lookup will be performed. -# warn: if a hostname is encountered, a reverse DNS lookup will be performed, -# but it will be logged as a warning. -# no: if a hostname is encountered, will not be used for banning, -# but it will be logged as info. -usedns = warn - -# -# Destination email address used solely for the interpolations in -# jail.{conf,local} configuration files. -destemail = root@localhost - -# -# Name of the sender for mta actions -sendername = Fail2Ban +# Common functions definitions -# -# ACTIONS -# - -# Default banning action (e.g. iptables, iptables-new, -# iptables-multiport, shorewall, etc) It is used to define -# action_* variables. Can be overridden globally or per -# section within jail.local file -banaction = iptables-multiport - -# email action. Since 0.8.1 upstream fail2ban uses sendmail -# MTA for the mailing. Change mta configuration parameter to mail -# if you want to revert to conventional 'mail'. -mta = sendmail - -# Default protocol -protocol = tcp - -# Specify chain where jumps would need to be added in iptables-* actions -chain = INPUT - -# -# Action shortcuts. To be used to define action parameter - -# The simplest action to take: ban only -action_ = %(banaction)s[name=%(__name__)s, port="%(port)s", protocol="%(protocol)s", chain="%(chain)s"] - -# ban & send an e-mail with whois report to the destemail. -action_mw = %(banaction)s[name=%(__name__)s, port="%(port)s", protocol="%(protocol)s", chain="%(chain)s"] - %(mta)s-whois[name=%(__name__)s, dest="%(destemail)s", protocol="%(protocol)s", chain="%(chain)s", sendername="%(sendername)s"] +function get_setup_params_from_configs_json +{ + local configs_json_path=${1} # E.g., /var/lib/cloud/instance/moodle_on_azure_configs.json + + # (dpkg -l jq &> /dev/null) || (apt -y update; apt -y install jq) + # sudo add-apt-repository universe + # sudo apt-get -y update + # sudo apt-get -y install jq + + # Added curl command to download jq. + curl https://stedolan.github.io/jq/download/linux64/jq > /usr/bin/jq && chmod +x /usr/bin/jq + + # Wait for the cloud-init write-files user data file to be generated (just in case) + local wait_time_sec=0 + while [ ! -f "$configs_json_path" ]; do + sleep 15 + let "wait_time_sec += 15" + if [ "$wait_time_sec" -ge "1800" ]; then + echo "Error: Cloud-init write-files didn't complete in 30 minutes!" + return 1 + fi + done + + local json=$(cat $configs_json_path) + export moodleVersion=$(echo $json | jq -r .moodleProfile.version) + export glusterNode=$(echo $json | jq -r .fileServerProfile.glusterVmName) + export glusterVolume=$(echo $json | jq -r .fileServerProfile.glusterVolName) + export siteFQDN=$(echo $json | jq -r .siteProfile.siteURL) + export httpsTermination=$(echo $json | jq -r .siteProfile.httpsTermination) + export dbIP=$(echo $json | jq -r .dbServerProfile.fqdn) + export moodledbname=$(echo $json | jq -r .moodleProfile.dbName) + export moodledbuser=$(echo $json | jq -r .moodleProfile.dbUser) + export moodledbpass=$(echo $json | jq -r .moodleProfile.dbPassword) + export adminpass=$(echo $json | jq -r .moodleProfile.adminPassword) + export dbadminlogin=$(echo $json | jq -r .dbServerProfile.adminLogin) + export dbadminloginazure=$(echo $json | jq -r .dbServerProfile.adminLoginAzure) + export dbadminpass=$(echo $json | jq -r .dbServerProfile.adminPassword) + export storageAccountName=$(echo $json | jq -r .moodleProfile.storageAccountName) + export storageAccountKey=$(echo $json | jq -r .moodleProfile.storageAccountKey) + export azuremoodledbuser=$(echo $json | jq -r .moodleProfile.dbUserAzure) + export redisDns=$(echo $json | jq -r .moodleProfile.redisDns) + export redisAuth=$(echo $json | jq -r .moodleProfile.redisKey) + export elasticVm1IP=$(echo $json | jq -r .moodleProfile.elasticVm1IP) + export installO365pluginsSwitch=$(echo $json | jq -r .moodleProfile.installO365pluginsSwitch) + export dbServerType=$(echo $json | jq -r .dbServerProfile.type) + export fileServerType=$(echo $json | jq -r .fileServerProfile.type) + export mssqlDbServiceObjectiveName=$(echo $json | jq -r .dbServerProfile.mssqlDbServiceObjectiveName) + export mssqlDbEdition=$(echo $json | jq -r .dbServerProfile.mssqlDbEdition) + export mssqlDbSize=$(echo $json | jq -r .dbServerProfile.mssqlDbSize) + export installObjectFsSwitch=$(echo $json | jq -r .moodleProfile.installObjectFsSwitch) + export installGdprPluginsSwitch=$(echo $json | jq -r .moodleProfile.installGdprPluginsSwitch) + export thumbprintSslCert=$(echo $json | jq -r .siteProfile.thumbprintSslCert) + export thumbprintCaCert=$(echo $json | jq -r .siteProfile.thumbprintCaCert) + export searchType=$(echo $json | jq -r .moodleProfile.searchType) + export azureSearchKey=$(echo $json | jq -r .moodleProfile.azureSearchKey) + export azureSearchNameHost=$(echo $json | jq -r .moodleProfile.azureSearchNameHost) + export tikaVmIP=$(echo $json | jq -r .moodleProfile.tikaVmIP) + export syslogServer=$(echo $json | jq -r .moodleProfile.syslogServer) + export webServerType=$(echo $json | jq -r .moodleProfile.webServerType) + export htmlLocalCopySwitch=$(echo $json | jq -r .moodleProfile.htmlLocalCopySwitch) + export nfsVmName=$(echo $json | jq -r .fileServerProfile.nfsVmName) + export nfsHaLbIP=$(echo $json | jq -r .fileServerProfile.nfsHaLbIP) + export nfsHaExportPath=$(echo $json | jq -r .fileServerProfile.nfsHaExportPath) + export nfsByoIpExportPath=$(echo $json | jq -r .fileServerProfile.nfsByoIpExportPath) + export storageAccountType=$(echo $json | jq -r .moodleProfile.storageAccountType) + export fileServerDiskSize=$(echo $json | jq -r .fileServerProfile.fileServerDiskSize) + export phpVersion=$(echo $json | jq -r .phpProfile.phpVersion) +} -# ban & send an e-mail with whois report and relevant log lines -# to the destemail. -action_mwl = %(banaction)s[name=%(__name__)s, port="%(port)s", protocol="%(protocol)s", chain="%(chain)s"] - %(mta)s-whois-lines[name=%(__name__)s, dest="%(destemail)s", logpath=%(logpath)s, chain="%(chain)s", sendername="%(sendername)s"] +function get_php_version { +# Returns current PHP version, in the form of x.x, eg 7.0 or 7.2 + if [ -z "$_PHPVER" ]; then + _PHPVER=`/usr/bin/php -r "echo PHP_VERSION;" | /usr/bin/cut -c 1,2,3` + fi + echo $_PHPVER +} -# Choose default action. To change, just override value of 'action' with the -# interpolation to the chosen action shortcut (e.g. action_mw, action_mwl, etc) in jail.local -# globally (section [DEFAULT]) or per specific section -action = %(action_)s +function install_php_mssql_driver +{ + # Download and build php/mssql driver + /usr/bin/curl https://packages.microsoft.com/keys/microsoft.asc | apt-key add - + /usr/bin/curl https://packages.microsoft.com/config/ubuntu/16.04/prod.list > /etc/apt/sources.list.d/mssql-release.list + sudo apt-get update + sudo ACCEPT_EULA=Y apt-get install msodbcsql mssql-tools unixodbc-dev -y + echo 'export PATH="$PATH:/opt/mssql-tools/bin"' >> ~/.bash_profile + echo 'export PATH="$PATH:/opt/mssql-tools/bin"' >> ~/.bashrc + source ~/.bashrc + + #Build mssql driver + /usr/bin/pear config-set php_ini `php --ini | grep "Loaded Configuration" | sed -e "s|.*:\s*||"` system + /usr/bin/pecl install sqlsrv + /usr/bin/pecl install pdo_sqlsrv + PHPVER=$(get_php_version) + echo "extension=sqlsrv.so" >> /etc/php/$PHPVER/fpm/php.ini + echo "extension=pdo_sqlsrv.so" >> /etc/php/$PHPVER/fpm/php.ini + echo "extension=sqlsrv.so" >> /etc/php/$PHPVER/apache2/php.ini + echo "extension=pdo_sqlsrv.so" >> /etc/php/$PHPVER/apache2/php.ini + echo "extension=sqlsrv.so" >> /etc/php/$PHPVER/cli/php.ini + echo "extension=pdo_sqlsrv.so" >> /etc/php/$PHPVER/cli/php.ini +} -# -# JAILS -# +function check_fileServerType_param +{ + local fileServerType=$1 + if [ "$fileServerType" != "gluster" -a "$fileServerType" != "azurefiles" -a "$fileServerType" != "nfs" -a "$fileServerType" != "nfs-ha" -a "$fileServerType" != "nfs-byo" ]; then + echo "Invalid fileServerType ($fileServerType) given. Only 'gluster', 'azurefiles', 'nfs', 'nfs-ha' or 'nfs-byo' are allowed. Exiting" + exit 1 + fi +} -# Next jails corresponds to the standard configuration in Fail2ban 0.6 which -# was shipped in Debian. Enable any defined here jail by including -# -# [SECTION_NAME] -# enabled = true +function create_azure_files_moodle_share +{ + local storageAccountName=$1 + local storageAccountKey=$2 + local logFilePath=$3 + local fileServerDiskSize=$4 + + az storage share create \ + --name moodle \ + --account-name $storageAccountName \ + --account-key $storageAccountKey \ + --fail-on-exist >>$logFilePath \ + --quota $fileServerDiskSize +} -# -# in /etc/fail2ban/jail.local. -# -# Optionally you may override any other parameter (e.g. banaction, -# action, port, logpath, etc) in that section within jail.local +function setup_and_mount_gluster_moodle_share +{ + local glusterNode=$1 + local glusterVolume=$2 -[ssh] + 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 + mount /moodle +} -enabled = true -port = ssh -filter = sshd -logpath = /var/log/auth.log -maxretry = 6 +function setup_and_mount_azure_files_moodle_share +{ + local storageAccountName=$1 + local storageAccountKey=$2 -[dropbear] + cat < /etc/moodle_azure_files.credential +username=$storageAccountName +password=$storageAccountKey +EOF + chmod 600 /etc/moodle_azure_files.credential + + grep -q -s "^//$storageAccountName.file.core.windows.net/moodle\s\s*/moodle\s\s*cifs" /etc/fstab && _RET=$? || _RET=$? + if [ $_RET != "0" ]; then + 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 + fi + mkdir -p /moodle + mount /moodle +} -enabled = false -port = ssh -filter = dropbear -logpath = /var/log/auth.log -maxretry = 6 +function setup_moodle_mount_dependency_for_systemd_service +{ + local serviceName=$1 # E.g., nginx, apache2 + if [ -z "$serviceName" ]; then + return 1 + fi + + local systemdSvcOverrideFileDir="/etc/systemd/system/${serviceName}.service.d" + local systemdSvcOverrideFilePath="${systemdSvcOverrideFileDir}/moodle_on_azure_override.conf" + + grep -q -s "After=moodle.mount" $systemdSvcOverrideFilePath && _RET=$? || _RET=$? + if [ $_RET != "0" ]; then + mkdir -p $systemdSvcOverrideFileDir + cat < $systemdSvcOverrideFilePath +[Unit] +After=moodle.mount +EOF + systemctl daemon-reload + fi +} -# Generic filter for pam. Has to be used with action which bans all ports -# such as iptables-allports, shorewall -[pam-generic] +# Functions for making NFS share available +# TODO refactor these functions with the same ones in install_gluster.sh +function scan_for_new_disks +{ + local BLACKLIST=${1} # E.g., /dev/sda|/dev/sdb + declare -a RET + local DEVS=$(ls -1 /dev/sd*|egrep -v "${BLACKLIST}"|egrep -v "[0-9]$") + for DEV in ${DEVS}; + do + # Check each device if there is a "1" partition. If not, + # "assume" it is not partitioned. + if [ ! -b ${DEV}1 ]; + then + RET+="${DEV} " + fi + done + echo "${RET}" +} -enabled = false -# pam-generic filter can be customized to monitor specific subset of 'tty's -filter = pam-generic -# port actually must be irrelevant but lets leave it all for some possible uses -port = all -banaction = iptables-allports -port = anyport -logpath = /var/log/auth.log -maxretry = 6 +function create_raid0_ubuntu { + local RAIDDISK=${1} # E.g., /dev/md1 + local RAIDCHUNKSIZE=${2} # E.g., 128 + local DISKCOUNT=${3} # E.g., 4 + shift + shift + shift + local DISKS="$@" + + dpkg -s mdadm && _RET=$? || _RET=$? + if [ $_RET -eq 1 ]; + then + echo "installing mdadm" + sudo apt-get -y -q install mdadm + fi + echo "Creating raid0" + udevadm control --stop-exec-queue + echo "yes" | mdadm --create $RAIDDISK --name=data --level=0 --chunk=$RAIDCHUNKSIZE --raid-devices=$DISKCOUNT $DISKS + udevadm control --start-exec-queue + mdadm --detail --verbose --scan > /etc/mdadm/mdadm.conf +} -[xinetd-fail] +function do_partition { + # This function creates one (1) primary partition on the + # disk device, using all available space + local DISK=${1} # E.g., /dev/sdc + + echo "Partitioning disk $DISK" + echo -ne "n\np\n1\n\n\nw\n" | fdisk "${DISK}" + #> /dev/null 2>&1 + + # + # Use the bash-specific $PIPESTATUS to ensure we get the correct exit code + # from fdisk and not from echo + if [ ${PIPESTATUS[1]} -ne 0 ]; + then + echo "An error occurred partitioning ${DISK}" >&2 + echo "I cannot continue" >&2 + exit 2 + fi +} -enabled = false -filter = xinetd-fail -port = all -banaction = iptables-multiport-log -logpath = /var/log/daemon.log -maxretry = 2 +function add_local_filesystem_to_fstab { + local UUID=${1} + local MOUNTPOINT=${2} # E.g., /moodle + + grep -q -s "${UUID}" /etc/fstab && _RET=$? || _RET=$? + if [ $_RET -eq 0 ]; + then + echo "Not adding ${UUID} to fstab again (it's already there!)" + else + LINE="\nUUID=${UUID} ${MOUNTPOINT} ext4 defaults,noatime 0 0" + echo -e "${LINE}" >> /etc/fstab + fi +} +function setup_raid_disk_and_filesystem { + local MOUNTPOINT=${1} # E.g., /moodle + local RAIDDISK=${2} # E.g., /dev/md1 + local RAIDPARTITION=${3} # E.g., /dev/md1p1 + local CREATE_FILESYSTEM=${4} # E.g., "" (true) or any non-empty string (false) + + local DISKS=$(scan_for_new_disks "/dev/sda|/dev/sdb") + echo "Disks are ${DISKS}" + declare -i DISKCOUNT + local DISKCOUNT=$(echo "$DISKS" | wc -w) + echo "Disk count is $DISKCOUNT" + if [ $DISKCOUNT = "0" ]; then + echo "No new (unpartitioned) disks available... Returning non-zero..." + return 1 + fi + + if [ $DISKCOUNT -gt 1 ]; then + create_raid0_ubuntu ${RAIDDISK} 128 $DISKCOUNT $DISKS + AZMDL_DISK=$RAIDDISK + if [ -z "$CREATE_FILESYSTEM" ]; then + do_partition ${RAIDDISK} + local PARTITION="${RAIDPARTITION}" + fi + else # Just one unpartitioned disk + AZMDL_DISK=$DISKS + if [ -z "$CREATE_FILESYSTEM" ]; then + do_partition ${DISKS} + local PARTITION=$(fdisk -l ${DISKS}|grep -A 1 Device|tail -n 1|awk '{print $1}') + fi + fi + + 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" + + if [ -z "$CREATE_FILESYSTEM" ]; then + echo "Creating filesystem on ${PARTITION}." + mkfs -t ext4 ${PARTITION} + mkdir -p "${MOUNTPOINT}" + local UUID=$(blkid -u filesystem ${PARTITION}|awk -F "[= ]" '{print $3}'|tr -d "\"") + add_local_filesystem_to_fstab "${UUID}" "${MOUNTPOINT}" + echo "Mounting disk ${PARTITION} on ${MOUNTPOINT}" + mount "${MOUNTPOINT}" + fi +} -[ssh-ddos] +function configure_nfs_server_and_export { + local MOUNTPOINT=${1} # E.g., /moodle -enabled = false -port = ssh -filter = sshd-ddos -logpath = /var/log/auth.log -maxretry = 6 + echo "Installing nfs server..." + apt install -y nfs-kernel-server + echo "Exporting ${MOUNTPOINT}..." + grep -q -s "^${MOUNTPOINT}" /etc/exports && _RET=$? || _RET=$? + if [ $_RET = "0" ]; then + echo "${MOUNTPOINT} is already exported. Returning..." + else + echo -e "\n${MOUNTPOINT} *(rw,sync,no_root_squash)" >> /etc/exports + systemctl restart nfs-kernel-server.service + fi +} -# Here we use blackhole routes for not requiring any additional kernel support -# to store large volumes of banned IPs +function configure_nfs_client_and_mount0 { + local NFS_HOST_EXPORT_PATH=${1} # E.g., controller-vm-ab12cd:/moodle or 172.16.3.100:/drbd/data + local MOUNTPOINT=${2} # E.g., /moodle -[ssh-route] + apt install -y nfs-common + mkdir -p ${MOUNTPOINT} -enabled = false -filter = sshd -action = route -logpath = /var/log/sshd.log -maxretry = 6 + grep -q -s "^${NFS_HOST_EXPORT_PATH}" /etc/fstab && _RET=$? || _RET=$? + if [ $_RET = "0" ]; then + echo "${NFS_HOST_EXPORT_PATH} already in /etc/fstab... skipping to add" + else + echo -e "\n${NFS_HOST_EXPORT_PATH} ${MOUNTPOINT} nfs auto 0 0" >> /etc/fstab + fi + mount ${MOUNTPOINT} +} -# Here we use a combination of Netfilter/Iptables and IPsets -# for storing large volumes of banned IPs -# -# IPset comes in two versions. See ipset -V for which one to use -# requires the ipset package and kernel support. -[ssh-iptables-ipset4] +function configure_nfs_client_and_mount { + local NFS_SERVER=${1} # E.g., controller-vm-ab12cd or IP (NFS-HA LB) + local NFS_DIR=${2} # E.g., /moodle or /drbd/data + local MOUNTPOINT=${3} # E.g., /moodle -enabled = false -port = ssh -filter = sshd -banaction = iptables-ipset-proto4 -logpath = /var/log/sshd.log -maxretry = 6 + configure_nfs_client_and_mount0 "${NFS_SERVER}:${NFS_DIR}" ${MOUNTPOINT} +} -[ssh-iptables-ipset6] +SERVER_TIMESTAMP_FULLPATH="/moodle/html/moodle/.last_modified_time.moodle_on_azure" +LOCAL_TIMESTAMP_FULLPATH="/var/www/html/moodle/.last_modified_time.moodle_on_azure" -enabled = false -port = ssh -filter = sshd -banaction = iptables-ipset-proto6 -logpath = /var/log/sshd.log -maxretry = 6 +# Create a script to sync /moodle/html/moodle (gluster/NFS) and /var/www/html/moodle (local) and set up a minutely cron job +# Should be called by root and only on a VMSS web frontend VM +function setup_html_local_copy_cron_job { + if [ "$(whoami)" != "root" ]; then + echo "${0}: Must be run as root!" + return 1 + fi + local SYNC_SCRIPT_FULLPATH="/usr/local/bin/sync_moodle_html_local_copy_if_modified.sh" + mkdir -p $(dirname ${SYNC_SCRIPT_FULLPATH}) -# -# HTTP servers -# + local SYNC_LOG_FULLPATH="/var/log/moodle-html-sync.log" -[apache] + cat < ${SYNC_SCRIPT_FULLPATH} +#!/bin/bash -enabled = false -port = http,https -filter = apache-auth -logpath = /var/log/apache*/*error.log -maxretry = 6 +sleep \$((\$RANDOM%30)) + +if [ -f "$SERVER_TIMESTAMP_FULLPATH" ]; then + SERVER_TIMESTAMP=\$(cat $SERVER_TIMESTAMP_FULLPATH) + if [ -f "$LOCAL_TIMESTAMP_FULLPATH" ]; then + LOCAL_TIMESTAMP=\$(cat $LOCAL_TIMESTAMP_FULLPATH) + else + logger -p local2.notice -t moodle "Local timestamp file ($LOCAL_TIMESTAMP_FULLPATH) does not exist. Probably first time syncing? Continuing to sync." + mkdir -p /var/www/html + fi + if [ "\$SERVER_TIMESTAMP" != "\$LOCAL_TIMESTAMP" ]; then + logger -p local2.notice -t moodle "Server time stamp (\$SERVER_TIMESTAMP) is different from local time stamp (\$LOCAL_TIMESTAMP). Start syncing..." + if [[ \$(find $SYNC_LOG_FULLPATH -type f -size +20M 2> /dev/null) ]]; then + truncate -s 0 $SYNC_LOG_FULLPATH + fi + echo \$(date +%Y%m%d%H%M%S) >> $SYNC_LOG_FULLPATH + rsync -av --delete /moodle/html/moodle /var/www/html >> $SYNC_LOG_FULLPATH + fi +else + logger -p local2.notice -t moodle "Remote timestamp file ($SERVER_TIMESTAMP_FULLPATH) does not exist. Is /moodle mounted? Exiting with error." + exit 1 +fi +EOF + chmod 500 ${SYNC_SCRIPT_FULLPATH} -# default action is now multiport, so apache-multiport jail was left -# for compatibility with previous (<0.7.6-2) releases -[apache-multiport] + local CRON_DESC_FULLPATH="/etc/cron.d/sync-moodle-html-local-copy" + cat < ${CRON_DESC_FULLPATH} +* * * * * root ${SYNC_SCRIPT_FULLPATH} +EOF + chmod 644 ${CRON_DESC_FULLPATH} -enabled = false -port = http,https -filter = apache-auth -logpath = /var/log/apache*/*error.log -maxretry = 6 + # Addition of a hook for custom script run on VMSS from shared mount to allow customised configuration of the VMSS as required + local CRON_DESC_FULLPATH2="/etc/cron.d/update-vmss-config" + cat < ${CRON_DESC_FULLPATH2} +* * * * * root [ -f /moodle/bin/update-vmss-config ] && /bin/bash /moodle/bin/update-vmss-config +EOF + chmod 644 ${CRON_DESC_FULLPATH2} +} -[apache-noscript] +LAST_MODIFIED_TIME_UPDATE_SCRIPT_FULLPATH="/usr/local/bin/update_last_modified_time.moodle_on_azure.sh" -enabled = false -port = http,https -filter = apache-noscript -logpath = /var/log/apache*/*error.log -maxretry = 6 +# Create a script to modify the last modified timestamp file (/moodle/html/moodle/last_modified_time.moodle_on_azure) +# Should be called by root and only on the controller VM. +# 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) +function create_last_modified_time_update_script { + if [ "$(whoami)" != "root" ]; then + echo "${0}: Must be run as root!" + return 1 + fi -[apache-overflows] + mkdir -p $(dirname $LAST_MODIFIED_TIME_UPDATE_SCRIPT_FULLPATH) + cat < $LAST_MODIFIED_TIME_UPDATE_SCRIPT_FULLPATH +#!/bin/bash +echo \$(date +%Y%m%d%H%M%S) > $SERVER_TIMESTAMP_FULLPATH +EOF -enabled = false -port = http,https -filter = apache-overflows -logpath = /var/log/apache*/*error.log -maxretry = 2 + chmod +x $LAST_MODIFIED_TIME_UPDATE_SCRIPT_FULLPATH +} -# Ban attackers that try to use PHP's URL-fopen() functionality -# through GET/POST variables. - Experimental, with more than a year -# of usage in production environments. +function run_once_last_modified_time_update_script { + $LAST_MODIFIED_TIME_UPDATE_SCRIPT_FULLPATH +} -[php-url-fopen] +# O365 plugins are released only for 'MOODLE_xy_STABLE', +# whereas we want to support the Moodle tagged versions (e.g., 'v3.4.2'). +# This function helps getting the stable version # (for O365 plugin ver.) +# from a Moodle version tag. This utility function recognizes tag names +# of the form 'vx.y.z' only. +function get_o365plugin_version_from_moodle_version { + local moodleVersion=${1} + if [[ "$moodleVersion" =~ v([0-9]+)\.([0-9]+)\.([0-9]+) ]]; then + echo "MOODLE_${BASH_REMATCH[1]}${BASH_REMATCH[2]}_STABLE" + else + echo $moodleVersion + fi +} -enabled = false -port = http,https -filter = php-url-fopen -logpath = /var/www/*/logs/access_log +# For Moodle tags (e.g., "v3.4.2"), the unzipped Moodle dir is no longer +# "moodle-$moodleVersion", because for tags, it's without "v". That is, +# it's "moodle-3.4.2". Therefore, we need a separate helper function for that... +function get_moodle_unzip_dir_from_moodle_version { + local moodleVersion=${1} + if [[ "$moodleVersion" =~ v([0-9]+)\.([0-9]+)\.([0-9]+) ]]; then + echo "moodle-${BASH_REMATCH[1]}.${BASH_REMATCH[2]}.${BASH_REMATCH[3]}" + else + echo "moodle-$moodleVersion" + fi +} -# A simple PHP-fastcgi jail which works with lighttpd. -# If you run a lighttpd server, then you probably will -# find these kinds of messages in your error_log: -# ALERT – tried to register forbidden variable ‘GLOBALS’ -# through GET variables (attacker '1.2.3.4', file '/var/www/default/htdocs/index.php') - -[lighttpd-fastcgi] - -enabled = false -port = http,https -filter = lighttpd-fastcgi -logpath = /var/log/lighttpd/error.log - -# Same as above for mod_auth -# It catches wrong authentifications - -[lighttpd-auth] - -enabled = false -port = http,https -filter = suhosin -logpath = /var/log/lighttpd/error.log - -[nginx-http-auth] - -enabled = false -filter = nginx-http-auth -port = http,https -logpath = /var/log/nginx/error.log - -# Monitor roundcube server - -[roundcube-auth] - -enabled = false -filter = roundcube-auth -port = http,https -logpath = /var/log/roundcube/userlogins - - -[sogo-auth] - -enabled = false -filter = sogo-auth -port = http, https -# without proxy this would be: -# port = 20000 -logpath = /var/log/sogo/sogo.log - - -# -# FTP servers -# - -[vsftpd] - -enabled = false -port = ftp,ftp-data,ftps,ftps-data -filter = vsftpd -logpath = /var/log/vsftpd.log -# or overwrite it in jails.local to be -# logpath = /var/log/auth.log -# if you want to rely on PAM failed login attempts -# vsftpd's failregex should match both of those formats -maxretry = 6 - - -[proftpd] - -enabled = false -port = ftp,ftp-data,ftps,ftps-data -filter = proftpd -logpath = /var/log/proftpd/proftpd.log -maxretry = 6 - - -[pure-ftpd] - -enabled = false -port = ftp,ftp-data,ftps,ftps-data -filter = pure-ftpd -logpath = /var/log/syslog -maxretry = 6 - - -[wuftpd] - -enabled = false -port = ftp,ftp-data,ftps,ftps-data -filter = wuftpd -logpath = /var/log/syslog -maxretry = 6 - - -# -# Mail servers -# - -[postfix] - -enabled = false -port = smtp,ssmtp,submission -filter = postfix -logpath = /var/log/mail.log - - -[couriersmtp] - -enabled = false -port = smtp,ssmtp,submission -filter = couriersmtp -logpath = /var/log/mail.log - - -# -# Mail servers authenticators: might be used for smtp,ftp,imap servers, so -# all relevant ports get banned -# - -[courierauth] - -enabled = false -port = smtp,ssmtp,submission,imap2,imap3,imaps,pop3,pop3s -filter = courierlogin -logpath = /var/log/mail.log - - -[sasl] - -enabled = false -port = smtp,ssmtp,submission,imap2,imap3,imaps,pop3,pop3s -filter = postfix-sasl -# You might consider monitoring /var/log/mail.warn instead if you are -# running postfix since it would provide the same log lines at the -# "warn" level but overall at the smaller filesize. -logpath = /var/log/mail.log - -[dovecot] - -enabled = false -port = smtp,ssmtp,submission,imap2,imap3,imaps,pop3,pop3s -filter = dovecot -logpath = /var/log/mail.log - -# To log wrong MySQL access attempts add to /etc/my.cnf: -# log-error=/var/log/mysqld.log -# log-warning = 2 -[mysqld-auth] - -enabled = false -filter = mysqld-auth -port = 3306 -logpath = /var/log/mysqld.log - - -# DNS Servers - - -# These jails block attacks against named (bind9). By default, logging is off -# with bind9 installation. You will need something like this: -# -# logging { -# channel security_file { -# file "/var/log/named/security.log" versions 3 size 30m; -# severity dynamic; -# print-time yes; -# }; -# category security { -# security_file; -# }; -# }; -# -# in your named.conf to provide proper logging - -# !!! WARNING !!! -# Since UDP is connection-less protocol, spoofing of IP and imitation -# of illegal actions is way too simple. Thus enabling of this filter -# might provide an easy way for implementing a DoS against a chosen -# victim. See -# http://nion.modprobe.de/blog/archives/690-fail2ban-+-dns-fail.html -# Please DO NOT USE this jail unless you know what you are doing. -#[named-refused-udp] -# -#enabled = false -#port = domain,953 -#protocol = udp -#filter = named-refused -#logpath = /var/log/named/security.log - -[named-refused-tcp] - -enabled = false -port = domain,953 -protocol = tcp -filter = named-refused -logpath = /var/log/named/security.log - -# Multiple jails, 1 per protocol, are necessary ATM: -# see https://github.com/fail2ban/fail2ban/issues/37 -[asterisk-tcp] - -enabled = false -filter = asterisk -port = 5060,5061 -protocol = tcp -logpath = /var/log/asterisk/messages - -[asterisk-udp] - -enabled = false -filter = asterisk -port = 5060,5061 -protocol = udp -logpath = /var/log/asterisk/messages - - -# Jail for more extended banning of persistent abusers -# !!! WARNING !!! -# Make sure that your loglevel specified in fail2ban.conf/.local -# is not at DEBUG level -- which might then cause fail2ban to fall into -# an infinite loop constantly feeding itself with non-informative lines -[recidive] - -enabled = false -filter = recidive -logpath = /var/log/fail2ban.log -action = iptables-allports[name=recidive] - sendmail-whois-lines[name=recidive, logpath=/var/log/fail2ban.log] -bantime = 604800 ; 1 week -findtime = 86400 ; 1 day -maxretry = 5 -EOF - - # create gluster mount point - mkdir -p /moodle - - export DEBIAN_FRONTEND=noninteractive - - # configure gluster repository & install gluster client - sudo add-apt-repository ppa:gluster/glusterfs-3.8 -y >> /tmp/apt1.log - sudo apt-get -y update >> /tmp/apt2.log - sudo apt-get -y --force-yes install rsyslog glusterfs-client mysql-client git >> /tmp/apt3.log - - # install azure cli & setup container - echo "deb [arch=amd64] https://packages.microsoft.com/repos/azure-cli/ wheezy main" | \ - sudo tee /etc/apt/sources.list.d/azure-cli.list - - sudo apt-key adv --keyserver packages.microsoft.com --recv-keys 52E16F86FEE04B979B07E28DB02C46DF417A0893 >> /tmp/apt4.log - sudo apt-get -y install apt-transport-https >> /tmp/apt4.log - sudo apt-get -y update > /dev/null - sudo apt-get -y install azure-cli >> /tmp/apt4.log - - az storage container create \ - --name objectfs \ - --account-name $wabsacctname \ - --account-key $wabsacctkey \ - --public-access off \ - --fail-on-exist >> /tmp/wabs.log - - az storage container policy create \ - --account-name $wabsacctname \ - --account-key $wabsacctkey \ - --container-name objectfs \ - --name readwrite \ - --start $(date --date="1 day ago" +%F) \ - --expiry $(date --date="2199-01-01" +%F) \ - --permissions rw >> /tmp/wabs.log - - sas=$(az storage container generate-sas \ - --account-name $wabsacctname \ - --account-key $wabsacctkey \ - --name objectfs \ - --policy readwrite \ - --output tsv) - - # mount gluster files system - echo -e '\n\rInstalling GlusterFS on '$glusterNode':/'$glusterVolume '/moodle\n\r' - sudo mount -t glusterfs $glusterNode:/$glusterVolume /moodle - - - - # install pre-requisites - sudo apt-get install -y --fix-missing python-software-properties unzip - - # install the entire stack - sudo apt-get -y --force-yes install nginx php-fpm varnish >> /tmp/apt5a.log - sudo apt-get -y --force-yes install php php-cli php-curl php-zip >> /tmp/apt5b.log - - # Moodle requirements - sudo apt-get -y update > /dev/null - sudo apt-get install -y --force-yes graphviz aspell php-common php-soap php-json php-redis > /tmp/apt6.log - sudo apt-get install -y --force-yes php-bcmath php-gd php-mysql php-xmlrpc php-intl php-xml php-bz2 >> /tmp/apt6.log - - - # Set up initial moodle dirs - mkdir -p /moodle/html - mkdir -p /moodle/certs - mkdir -p /moodle/moodledata - chown -R www-data.www-data /moodle - - # install Moodle - echo '#!/bin/bash - cd /tmp - - # downloading moodle - /usr/bin/curl -k --max-redirs 10 https://github.com/moodle/moodle/archive/'$moodleVersion'.zip -L -o moodle.zip - /usr/bin/unzip -q moodle.zip - /bin/mv -v moodle-'$moodleVersion' /moodle/html/moodle - - # Commented out as the plugin is not available - # install Office 365 plugins - #if [ "$installOfficePlugins" = "True" ]; then - # curl -k --max-redirs 10 https://github.com/Microsoft/o365-moodle/archive/'$moodleVersion'.zip -L -o o365.zip - # unzip -q o365.zip - # cp -r o365-moodle-'$moodleVersion'/* /moodle/html/moodle - # rm -rf o365-moodle-'$moodleVersion' - #fi - - # Install ElasticSearch plugin - /usr/bin/curl -k --max-redirs 10 https://github.com/catalyst/moodle-search_elastic/archive/master.zip -L -o plugin-elastic.zip - /usr/bin/unzip -q plugin-elastic.zip - /bin/mkdir -p /moodle/html/moodle/search/engine/elastic - /bin/cp -r moodle-search_elastic-master/* /moodle/html/moodle/search/engine/elastic - /bin/rm -rf moodle-search_elastic-master - - # Install ElasticSearch plugin dependency - /usr/bin/curl -k --max-redirs 10 https://github.com/catalyst/moodle-local_aws/archive/master.zip -L -o local-aws.zip - /usr/bin/unzip -q local-aws.zip - /bin/mkdir -p /moodle/html/moodle/local/aws - /bin/cp -r moodle-local_aws-master/* /moodle/html/moodle/local/aws - - # Install the ObjectFS plugin - /usr/bin/curl -k --max-redirs 10 https://github.com/catalyst/moodle-tool_objectfs/archive/master.zip -L -o plugin-objectfs.zip - /usr/bin/unzip -q plugin-objectfs.zip - /bin/mkdir -p /moodle/html/moodle/admin/tool/objectfs - /bin/cp -r moodle-tool_objectfs-master/* /moodle/html/moodle/admin/tool/objectfs - /bin/rm -rf moodle-tool_objectfs-master - - # Install the ObjectFS Azure library - /usr/bin/curl -k --max-redirs 10 https://github.com/catalyst/moodle-local_azure_storage/archive/master.zip -L -o plugin-azurelibrary.zip - /usr/bin/unzip -q plugin-azurelibrary.zip - /bin/mkdir -p /moodle/html/moodle/local/azure_storage - /bin/cp -r moodle-local_azure_storage-master/* /moodle/html/moodle/local/azure_storage - /bin/rm -rf moodle-local_azure_storage-master - ' > /tmp/setup-moodle.sh - - chmod 755 /tmp/setup-moodle.sh - sudo -u www-data /tmp/setup-moodle.sh >> /tmp/setupmoodle.log - - # create cron entry - # It is scheduled for once per day. It can be changed as needed. - 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 - - # Build nginx config - cat < /etc/nginx/nginx.conf -user www-data; -worker_processes 2; -pid /run/nginx.pid; - -events { - worker_connections 768; -} - -http { - - sendfile on; - tcp_nopush on; - tcp_nodelay on; - keepalive_timeout 65; - types_hash_max_size 2048; - client_max_body_size 0; - proxy_max_temp_file_size 0; - server_names_hash_bucket_size 128; - fastcgi_buffers 16 16k; - fastcgi_buffer_size 32k; - proxy_buffering off; - include /etc/nginx/mime.types; - default_type application/octet-stream; - - access_log /var/log/nginx/access.log; - error_log /var/log/nginx/error.log; - - set_real_ip_from 127.0.0.1; - real_ip_header X-Forwarded-For; - ssl_protocols TLSv1 TLSv1.1 TLSv1.2; # Dropping SSLv3, ref: POODLE - ssl_prefer_server_ciphers on; - - gzip on; - gzip_disable "msie6"; - gzip_vary on; - gzip_proxied any; - gzip_comp_level 6; - gzip_buffers 16 8k; - gzip_http_version 1.1; - gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript; - - map \$http_x_forwarded_proto \$fastcgi_https { - default \$https; - http ''; - https on; - } - - log_format moodle_combined '\$remote_addr - \$upstream_http_x_moodleuser [\$time_local] ' - '"\$request" \$status \$body_bytes_sent ' - '"\$http_referer" "\$http_user_agent"'; - - - include /etc/nginx/conf.d/*.conf; - include /etc/nginx/sites-enabled/*; -} -EOF - - cat <> /etc/nginx/sites-enabled/${siteFQDN}.conf -server { - listen 81 default; - server_name ${siteFQDN}; - root /moodle/html/moodle; - index index.php index.html index.htm; - - # Log to syslog - error_log syslog:server=localhost,facility=local1,severity=error,tag=moodle; - access_log syslog:server=localhost,facility=local1,severity=notice,tag=moodle moodle_combined; - - # Log XFF IP instead of varnish - set_real_ip_from 10.0.0.0/8; - set_real_ip_from 127.0.0.1; - set_real_ip_from 172.16.0.0/12; - set_real_ip_from 192.168.0.0/16; - real_ip_header X-Forwarded-For; - real_ip_recursive on; - - - # Redirect to https - if (\$http_x_forwarded_proto != https) { - return 301 https://\$server_name\$request_uri; - } - rewrite ^/(.*\.php)(/)(.*)$ /\$1?file=/\$3 last; - - - # Filter out php-fpm status page - location ~ ^/server-status { - return 404; - } - - location / { - try_files \$uri \$uri/index.php?\$query_string; - } - - location ~ [^/]\.php(/|$) { - fastcgi_split_path_info ^(.+?\.php)(/.*)$; - if (!-f \$document_root\$fastcgi_script_name) { - return 404; - } - - fastcgi_buffers 16 16k; - fastcgi_buffer_size 32k; - fastcgi_param SCRIPT_FILENAME \$document_root\$fastcgi_script_name; - fastcgi_pass unix:/run/php/php7.0-fpm.sock; - fastcgi_read_timeout 3600; - fastcgi_index index.php; - include fastcgi_params; - } -} - -server { - listen 443 ssl; - root /moodle/html/moodle; - index index.php index.html index.htm; - - ssl on; - ssl_certificate /moodle/certs/nginx.crt; - ssl_certificate_key /moodle/certs/nginx.key; - - # Log to syslog - error_log syslog:server=localhost,facility=local1,severity=error,tag=moodle; - access_log syslog:server=localhost,facility=local1,severity=notice,tag=moodle moodle_combined; - - # Log XFF IP instead of varnish - set_real_ip_from 10.0.0.0/8; - set_real_ip_from 127.0.0.1; - set_real_ip_from 172.16.0.0/12; - set_real_ip_from 192.168.0.0/16; - real_ip_header X-Forwarded-For; - real_ip_recursive on; - - location / { - proxy_set_header Host \$host; - proxy_set_header HTTP_REFERER \$http_referer; - proxy_set_header X-Forwarded-Host \$host; - proxy_set_header X-Forwarded-Server \$host; - proxy_set_header X-Forwarded-Proto https; - proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for; - proxy_pass http://localhost:80; - } -} -EOF - - echo -e "Generating SSL self-signed certificate" - openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout /moodle/certs/nginx.key -out /moodle/certs/nginx.crt -subj "/C=BR/ST=SP/L=SaoPaulo/O=IT/CN=$siteFQDN" - - # php config - PhpIni=/etc/php/7.0/fpm/php.ini - sed -i "s/memory_limit.*/memory_limit = 512M/" $PhpIni - sed -i "s/max_execution_time.*/max_execution_time = 18000/" $PhpIni - sed -i "s/max_input_vars.*/max_input_vars = 100000/" $PhpIni - sed -i "s/max_input_time.*/max_input_time = 600/" $PhpIni - sed -i "s/upload_max_filesize.*/upload_max_filesize = 1024M/" $PhpIni - sed -i "s/post_max_size.*/post_max_size = 1056M/" $PhpIni - sed -i "s/;opcache.use_cwd.*/opcache.use_cwd = 1/" $PhpIni - sed -i "s/;opcache.validate_timestamps.*/opcache.validate_timestamps = 1/" $PhpIni - sed -i "s/;opcache.save_comments.*/opcache.save_comments = 1/" $PhpIni - sed -i "s/;opcache.enable_file_override.*/opcache.enable_file_override = 0/" $PhpIni - sed -i "s/;opcache.enable.*/opcache.enable = 1/" $PhpIni - sed -i "s/;opcache.memory_consumption.*/opcache.memory_consumption = 256/" $PhpIni - sed -i "s/;opcache.max_accelerated_files.*/opcache.max_accelerated_files = 8000/" $PhpIni - - # fpm config - overload this - cat < /etc/php/7.0/fpm/pool.d/www.conf -[www] -user = www-data -group = www-data -listen = /run/php/php7.0-fpm.sock -listen.owner = www-data -listen.group = www-data -pm = dynamic -pm.max_children = 3000 -pm.start_servers = 20 -pm.min_spare_servers = 22 -pm.max_spare_servers = 30 -EOF - - # Remove the default site. Moodle is the only site we want - rm -f /etc/nginx/sites-enabled/default - - # restart Nginx - sudo service nginx restart - - # Configure varnish startup for 16.04 - 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" - sed -i "s/^ExecStart.*/${VARNISHSTART}/" /lib/systemd/system/varnish.service - - # Configure varnish VCL for moodle - cat <> /etc/varnish/moodle.vcl -vcl 4.0; - -import std; -import directors; -backend default { - .host = "localhost"; - .port = "81"; - .first_byte_timeout = 3600s; - .connect_timeout = 600s; - .between_bytes_timeout = 600s; -} - -sub vcl_recv { - # Varnish does not support SPDY or HTTP/2.0 untill we upgrade to Varnish 5.0 - if (req.method == "PRI") { - return (synth(405)); - } - - if (req.restarts == 0) { - if (req.http.X-Forwarded-For) { - set req.http.X-Forwarded-For = req.http.X-Forwarded-For + ", " + client.ip; - } else { - set req.http.X-Forwarded-For = client.ip; - } - } - - # Non-RFC2616 or CONNECT HTTP requests methods filtered. Pipe requests directly to backend - if (req.method != "GET" && - req.method != "HEAD" && - req.method != "PUT" && - req.method != "POST" && - req.method != "TRACE" && - req.method != "OPTIONS" && - req.method != "DELETE") { - return (pipe); - } - - # Varnish don't mess with healthchecks - if (req.url ~ "^/admin/tool/heartbeat" || req.url ~ "^/healthcheck.php") - { - return (pass); - } - - # Pipe requests to backup.php straight to backend - prevents problem with progress bar long polling 503 problem - # This is here because backup.php is POSTing to itself - Filter before !GET&&!HEAD - if (req.url ~ "^/backup/backup.php") - { - return (pipe); - } - - # Varnish only deals with GET and HEAD by default. If request method is not GET or HEAD, pass request to backend - if (req.method != "GET" && req.method != "HEAD") { - return (pass); - } - - ### Rules for Moodle and Totara sites ### - # Moodle doesn't require Cookie to serve following assets. Remove Cookie header from request, so it will be looked up. - if ( req.url ~ "^/altlogin/.+/.+\.(png|jpg|jpeg|gif|css|js|webp)$" || - req.url ~ "^/pix/.+\.(png|jpg|jpeg|gif)$" || - req.url ~ "^/theme/font.php" || - req.url ~ "^/theme/image.php" || - req.url ~ "^/theme/javascript.php" || - req.url ~ "^/theme/jquery.php" || - req.url ~ "^/theme/styles.php" || - req.url ~ "^/theme/yui" || - req.url ~ "^/lib/javascript.php/-1/" || - req.url ~ "^/lib/requirejs.php/-1/" - ) - { - set req.http.X-Long-TTL = "86400"; - unset req.http.Cookie; - return(hash); - } - - # Perform lookup for selected assets that we know are static but Moodle still needs a Cookie - if( req.url ~ "^/theme/.+\.(png|jpg|jpeg|gif|css|js|webp)" || - req.url ~ "^/lib/.+\.(png|jpg|jpeg|gif|css|js|webp)" || - req.url ~ "^/pluginfile.php/[0-9]+/course/overviewfiles/.+\.(?i)(png|jpg)$" - ) - { - # Set internal temporary header, based on which we will do things in vcl_backend_response - set req.http.X-Long-TTL = "86400"; - return (hash); - } - - # Serve requests to SCORM checknet.txt from varnish. Have to remove get parameters. Response body always contains "1" - if ( req.url ~ "^/lib/yui/build/moodle-core-checknet/assets/checknet.txt" ) - { - set req.url = regsub(req.url, "(.*)\?.*", "\1"); - unset req.http.Cookie; # Will go to hash anyway at the end of vcl_recv - set req.http.X-Long-TTL = "86400"; - return(hash); - } - - # Requests containing "Cookie" or "Authorization" headers will not be cached - if (req.http.Authorization || req.http.Cookie) { - return (pass); - } - - # Almost everything in Moodle correctly serves Cache-Control headers, if - # needed, which varnish will honor, but there are some which don't. Rather - # than explicitly finding them all and listing them here we just fail safe - # and don't cache unknown urls that get this far. - return (pass); -} - -sub vcl_backend_response { - # Happens after we have read the response headers from the backend. - # - # Here you clean the response headers, removing silly Set-Cookie headers - # and other mistakes your backend does. - - # We know these assest are static, let's set TTL >0 and allow client caching - 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 ) - { # If max-age < defined in X-Long-TTL header - set beresp.http.X-Orig-Pragma = beresp.http.Pragma; unset beresp.http.Pragma; - set beresp.http.X-Orig-Cache-Control = beresp.http.Cache-Control; - set beresp.http.Cache-Control = "public, max-age="+bereq.http.X-Long-TTL+", no-transform"; - set beresp.ttl = std.duration(bereq.http.X-Long-TTL + "s", 1s); - unset bereq.http.X-Long-TTL; - } - else if( !beresp.http.Cache-Control && bereq.http.X-Long-TTL && !beresp.http.WWW-Authenticate ) { - set beresp.http.X-Orig-Pragma = beresp.http.Pragma; unset beresp.http.Pragma; - set beresp.http.Cache-Control = "public, max-age="+bereq.http.X-Long-TTL+", no-transform"; - set beresp.ttl = std.duration(bereq.http.X-Long-TTL + "s", 1s); - unset bereq.http.X-Long-TTL; - } - else { # Don't touch headers if max-age > defined in X-Long-TTL header - unset bereq.http.X-Long-TTL; - } - - # Here we set X-Trace header, prepending it to X-Trace header received from backend. Useful for troubleshooting - if(beresp.http.x-trace && !beresp.was_304) { - 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; - } - else { - set beresp.http.X-Trace = regsub(server.identity, "^([^.]+),?.*$", "\1")+"->"+regsub(beresp.backend.name, "^(.+)\((?:[0-9]{1,3}\.){3}([0-9]{1,3})\)","\1(\2)"); - } - - # Gzip JS, CSS is done at the ngnix level doing it here dosen't respect the no buffer requsets - # if (beresp.http.content-type ~ "application/javascript.*" || beresp.http.content-type ~ "text") { - # set beresp.do_gzip = true; - #} -} - -sub vcl_deliver { - - # Revert back to original Cache-Control header before delivery to client - if (resp.http.X-Orig-Cache-Control) - { - set resp.http.Cache-Control = resp.http.X-Orig-Cache-Control; - unset resp.http.X-Orig-Cache-Control; - } - - # Revert back to original Pragma header before delivery to client - if (resp.http.X-Orig-Pragma) - { - set resp.http.Pragma = resp.http.X-Orig-Pragma; - unset resp.http.X-Orig-Pragma; - } - - # (Optional) X-Cache HTTP header will be added to responce, indicating whether object was retrieved from backend, or served from cache - if (obj.hits > 0) { - set resp.http.X-Cache = "HIT"; - } else { - set resp.http.X-Cache = "MISS"; - } - - # Set X-AuthOK header when totara/varnsih authentication succeeded - if (req.http.X-AuthOK) { - set resp.http.X-AuthOK = req.http.X-AuthOK; - } - - # If desired "Via: 1.1 Varnish-v4" response header can be removed from response - unset resp.http.Via; - unset resp.http.Server; - - return(deliver); -} - -sub vcl_backend_error { - # More comprehensive varnish error page. Display time, instance hostname, host header, url for easier troubleshooting. - set beresp.http.Content-Type = "text/html; charset=utf-8"; - set beresp.http.Retry-After = "5"; - synthetic( {" - - - - "} + beresp.status + " " + beresp.reason + {" - - -

Error "} + beresp.status + " " + beresp.reason + {"

-

"} + beresp.reason + {"

-

Guru Meditation:

-

Time: "} + now + {"

-

Node: "} + server.hostname + {"

-

Host: "} + bereq.http.host + {"

-

URL: "} + bereq.url + {"

-

XID: "} + bereq.xid + {"

-
-

Varnish cache server - - - "} ); - return (deliver); -} - -sub vcl_synth { - - #Redirect using '301 - Permanent Redirect', permanent redirect - if (resp.status == 851) { - set resp.http.Location = req.http.x-redir; - set resp.http.X-Varnish-Redirect = true; - set resp.status = 301; - return (deliver); - } - - #Redirect using '302 - Found', temporary redirect - if (resp.status == 852) { - set resp.http.Location = req.http.x-redir; - set resp.http.X-Varnish-Redirect = true; - set resp.status = 302; - return (deliver); - } - - #Redirect using '307 - Temporary Redirect', !GET&&!HEAD requests, dont change method on redirected requests - if (resp.status == 857) { - set resp.http.Location = req.http.x-redir; - set resp.http.X-Varnish-Redirect = true; - set resp.status = 307; - return (deliver); - } - - #Respond with 403 - Forbidden - if (resp.status == 863) { - set resp.http.X-Varnish-Error = true; - set resp.status = 403; - return (deliver); - } -} -EOF - - # Restart Varnish - systemctl daemon-reload - service varnish restart - - mysql -h $mysqlIP -u $mysqladminlogin -p${mysqladminpass} -e "CREATE DATABASE ${moodledbname} CHARACTER SET utf8;" - mysql -h $mysqlIP -u $mysqladminlogin -p${mysqladminpass} -e "GRANT ALL ON ${moodledbname}.* TO ${moodledbuser} IDENTIFIED BY '${moodledbpass}';" - - echo "mysql -h $mysqlIP -u $mysqladminlogin -p${mysqladminpass} -e \"CREATE DATABASE ${moodledbname};\"" >> /tmp/debug - echo "mysql -h $mysqlIP -u $mysqladminlogin -p${mysqladminpass} -e \"GRANT ALL ON ${moodledbname}.* TO ${moodledbuser} IDENTIFIED BY '${moodledbpass}';\"" >> /tmp/debug - - # Master config for syslog - mkdir /var/log/sitelogs - chown syslog.adm /var/log/sitelogs - cat <> /etc/rsyslog.conf -\$ModLoad imudp -\$UDPServerRun 514 -EOF - cat <> /etc/rsyslog.d/40-sitelogs.conf -local1.* /var/log/sitelogs/moodle/access.log -local1.err /var/log/sitelogs/moodle/error.log -local2.* /var/log/sitelogs/moodle/cron.log -EOF - service rsyslog restart - - # Fire off moodle setup - echo -e "cd /tmp; sudo -u www-data /usr/bin/php /moodle/html/moodle/admin/cli/install.php --chmod=770 --lang=en_us --wwwroot=https://"$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 " - cd /tmp; sudo -u www-data /usr/bin/php /moodle/html/moodle/admin/cli/install.php --chmod=770 --lang=en_us --wwwroot=https://$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 - - mysql -h $mysqlIP -u $mysqladminlogin -p${mysqladminpass} ${moodledbname} -e "INSERT INTO mdl_config_plugins (plugin, name, value) VALUES ('tool_objectfs', 'enabletasks', 1);" - 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');" - mysql -h $mysqlIP -u $mysqladminlogin -p${mysqladminpass} ${moodledbname} -e "INSERT INTO mdl_config_plugins (plugin, name, value) VALUES ('tool_objectfs', 'azure_accountname', '${wabsacctname}');" - mysql -h $mysqlIP -u $mysqladminlogin -p${mysqladminpass} ${moodledbname} -e "INSERT INTO mdl_config_plugins (plugin, name, value) VALUES ('tool_objectfs', 'azure_container', 'objectfs');" - mysql -h $mysqlIP -u $mysqladminlogin -p${mysqladminpass} ${moodledbname} -e "INSERT INTO mdl_config_plugins (plugin, name, value) VALUES ('tool_objectfs', 'azure_sastoken', '${sas}');" - - echo -e "\n\rDone! Installation completed!\n\r" - - # create redis configuration in /moodle/moodledata/muc/config.php - cat < /moodle/moodledata/muc/config.php +# Long Redis cache Moodle config file generation code moved here +function create_redis_configuration_in_moodledata_muc_config_php +{ + # create redis configuration in /moodle/moodledata/muc/config.php + cat < /moodle/moodledata/muc/config.php '7a142be09ea65699e4a6f6ef91c0773c', @@ -2076,42 +1385,494 @@ EOF ), ); EOF - # redis configuration in /moodle/html/moodle/config.php - sed -i "23 a \$CFG->session_redis_lock_expire = 7200;" /moodle/html/moodle/config.php - sed -i "23 a \$CFG->session_redis_acquire_lock_timeout = 120;" /moodle/html/moodle/config.php - sed -i "23 a \$CFG->session_redis_prefix = 'moodle_prod'; // Optional, default is don't set one." /moodle/html/moodle/config.php - sed -i "23 a \$CFG->session_redis_database = 0; // Optional, default is db 0." /moodle/html/moodle/config.php - sed -i "23 a \$CFG->session_redis_port = 6379; // Optional." /moodle/html/moodle/config.php - sed -i "23 a \$CFG->session_redis_host = '$redisDns';" /moodle/html/moodle/config.php - sed -i "23 a \$CFG->session_redis_auth = '$redisAuth';" /moodle/html/moodle/config.php - sed -i "23 a \$CFG->session_handler_class = '\\\core\\\session\\\redis';" /moodle/html/moodle/config.php - - # We proxy ssl, so moodle needs to know this - sed -i "23 a \$CFG->sslproxy = 'true';" /moodle/html/moodle/config.php - - # Set up elasticsearch plugin - sed -i "23 a \$CFG->forced_plugin_settings = ['search_elastic' => ['hostname' => 'http://$elasticVm1IP']];" /moodle/html/moodle/config.php - sed -i "23 a \$CFG->searchengine = 'elastic';" /moodle/html/moodle/config.php - sed -i "23 a \$CFG->enableglobalsearch = 'true';" /moodle/html/moodle/config.php - - # Set the ObjectFS alternate filesystem - sed -i "23 a \$CFG->alternative_file_system_class = '\\\tool_objectfs\\\azure_file_system';" /moodle/html/moodle/config.php - - # Set up cronned sql dump - cat < /etc/cron.d/sql-backup - 22 02 * * * root /usr/bin/mysqldump -h $mysqlIP -u ${azuremoodledbuser} -p'${moodledbpass}' --databases ${moodledbname} | gzip > /moodle/db-backup.sql.gz -EOF +} + +# Long fail2ban config command moved here +function config_fail2ban +{ + cat < /etc/fail2ban/jail.conf +# Fail2Ban configuration file. +# +# This file was composed for Debian systems from the original one +# provided now under /usr/share/doc/fail2ban/examples/jail.conf +# for additional examples. +# +# Comments: use '#' for comment lines and ';' for inline comments +# +# To avoid merges during upgrades DO NOT MODIFY THIS FILE +# and rather provide your changes in /etc/fail2ban/jail.local +# + +# The DEFAULT allows a global definition of the options. They can be overridden +# in each jail afterwards. + +[DEFAULT] + +# "ignoreip" can be an IP address, a CIDR mask or a DNS host. Fail2ban will not +# ban a host which matches an address in this list. Several addresses can be +# defined using space separator. +ignoreip = 127.0.0.1/8 + +# "bantime" is the number of seconds that a host is banned. +bantime = 600 + +# A host is banned if it has generated "maxretry" during the last "findtime" +# seconds. +findtime = 600 +maxretry = 3 + +# "backend" specifies the backend used to get files modification. +# Available options are "pyinotify", "gamin", "polling" and "auto". +# This option can be overridden in each jail as well. +# +# pyinotify: requires pyinotify (a file alteration monitor) to be installed. +# If pyinotify is not installed, Fail2ban will use auto. +# gamin: requires Gamin (a file alteration monitor) to be installed. +# If Gamin is not installed, Fail2ban will use auto. +# polling: uses a polling algorithm which does not require external libraries. +# auto: will try to use the following backends, in order: +# pyinotify, gamin, polling. +backend = auto + +# "usedns" specifies if jails should trust hostnames in logs, +# warn when reverse DNS lookups are performed, or ignore all hostnames in logs +# +# yes: if a hostname is encountered, a reverse DNS lookup will be performed. +# warn: if a hostname is encountered, a reverse DNS lookup will be performed, +# but it will be logged as a warning. +# no: if a hostname is encountered, will not be used for banning, +# but it will be logged as info. +usedns = warn + +# +# Destination email address used solely for the interpolations in +# jail.{conf,local} configuration files. +destemail = root@localhost + +# +# Name of the sender for mta actions +sendername = Fail2Ban + +# +# ACTIONS +# + +# Default banning action (e.g. iptables, iptables-new, +# iptables-multiport, shorewall, etc) It is used to define +# action_* variables. Can be overridden globally or per +# section within jail.local file +banaction = iptables-multiport + +# email action. Since 0.8.1 upstream fail2ban uses sendmail +# MTA for the mailing. Change mta configuration parameter to mail +# if you want to revert to conventional 'mail'. +mta = sendmail + +# Default protocol +protocol = tcp + +# Specify chain where jumps would need to be added in iptables-* actions +chain = INPUT + +# +# Action shortcuts. To be used to define action parameter + +# The simplest action to take: ban only +action_ = %(banaction)s[name=%(__name__)s, port="%(port)s", protocol="%(protocol)s", chain="%(chain)s"] + +# ban & send an e-mail with whois report to the destemail. +action_mw = %(banaction)s[name=%(__name__)s, port="%(port)s", protocol="%(protocol)s", chain="%(chain)s"] + %(mta)s-whois[name=%(__name__)s, dest="%(destemail)s", protocol="%(protocol)s", chain="%(chain)s", sendername="%(sendername)s"] + +# ban & send an e-mail with whois report and relevant log lines +# to the destemail. +action_mwl = %(banaction)s[name=%(__name__)s, port="%(port)s", protocol="%(protocol)s", chain="%(chain)s"] + %(mta)s-whois-lines[name=%(__name__)s, dest="%(destemail)s", logpath=%(logpath)s, chain="%(chain)s", sendername="%(sendername)s"] + +# Choose default action. To change, just override value of 'action' with the +# interpolation to the chosen action shortcut (e.g. action_mw, action_mwl, etc) in jail.local +# globally (section [DEFAULT]) or per specific section +action = %(action_)s + +# +# JAILS +# + +# Next jails corresponds to the standard configuration in Fail2ban 0.6 which +# was shipped in Debian. Enable any defined here jail by including +# +# [SECTION_NAME] +# enabled = true + +# +# in /etc/fail2ban/jail.local. +# +# Optionally you may override any other parameter (e.g. banaction, +# action, port, logpath, etc) in that section within jail.local + +[ssh] + +enabled = true +port = ssh +filter = sshd +logpath = /var/log/auth.log +maxretry = 6 + +[dropbear] + +enabled = false +port = ssh +filter = dropbear +logpath = /var/log/auth.log +maxretry = 6 + +# Generic filter for pam. Has to be used with action which bans all ports +# such as iptables-allports, shorewall +[pam-generic] + +enabled = false +# pam-generic filter can be customized to monitor specific subset of 'tty's +filter = pam-generic +# port actually must be irrelevant but lets leave it all for some possible uses +port = all +banaction = iptables-allports +port = anyport +logpath = /var/log/auth.log +maxretry = 6 + +[xinetd-fail] + +enabled = false +filter = xinetd-fail +port = all +banaction = iptables-multiport-log +logpath = /var/log/daemon.log +maxretry = 2 + + +[ssh-ddos] + +enabled = false +port = ssh +filter = sshd-ddos +logpath = /var/log/auth.log +maxretry = 6 + + +# Here we use blackhole routes for not requiring any additional kernel support +# to store large volumes of banned IPs + +[ssh-route] + +enabled = false +filter = sshd +action = route +logpath = /var/log/sshd.log +maxretry = 6 + +# Here we use a combination of Netfilter/Iptables and IPsets +# for storing large volumes of banned IPs +# +# IPset comes in two versions. See ipset -V for which one to use +# requires the ipset package and kernel support. +[ssh-iptables-ipset4] + +enabled = false +port = ssh +filter = sshd +banaction = iptables-ipset-proto4 +logpath = /var/log/sshd.log +maxretry = 6 + +[ssh-iptables-ipset6] + +enabled = false +port = ssh +filter = sshd +banaction = iptables-ipset-proto6 +logpath = /var/log/sshd.log +maxretry = 6 + + +# +# HTTP servers +# + +[apache] + +enabled = false +port = http,https +filter = apache-auth +logpath = /var/log/apache*/*error.log +maxretry = 6 + +# default action is now multiport, so apache-multiport jail was left +# for compatibility with previous (<0.7.6-2) releases +[apache-multiport] + +enabled = false +port = http,https +filter = apache-auth +logpath = /var/log/apache*/*error.log +maxretry = 6 + +[apache-noscript] + +enabled = false +port = http,https +filter = apache-noscript +logpath = /var/log/apache*/*error.log +maxretry = 6 + +[apache-overflows] + +enabled = false +port = http,https +filter = apache-overflows +logpath = /var/log/apache*/*error.log +maxretry = 2 + +# Ban attackers that try to use PHP's URL-fopen() functionality +# through GET/POST variables. - Experimental, with more than a year +# of usage in production environments. + +[php-url-fopen] + +enabled = false +port = http,https +filter = php-url-fopen +logpath = /var/www/*/logs/access_log + +# A simple PHP-fastcgi jail which works with lighttpd. +# If you run a lighttpd server, then you probably will +# find these kinds of messages in your error_log: +# ALERT – tried to register forbidden variable ‘GLOBALS’ +# through GET variables (attacker '1.2.3.4', file '/var/www/default/htdocs/index.php') + +[lighttpd-fastcgi] + +enabled = false +port = http,https +filter = lighttpd-fastcgi +logpath = /var/log/lighttpd/error.log + +# Same as above for mod_auth +# It catches wrong authentifications + +[lighttpd-auth] + +enabled = false +port = http,https +filter = suhosin +logpath = /var/log/lighttpd/error.log + +[nginx-http-auth] + +enabled = false +filter = nginx-http-auth +port = http,https +logpath = /var/log/nginx/error.log + +# Monitor roundcube server + +[roundcube-auth] + +enabled = false +filter = roundcube-auth +port = http,https +logpath = /var/log/roundcube/userlogins + + +[sogo-auth] + +enabled = false +filter = sogo-auth +port = http, https +# without proxy this would be: +# port = 20000 +logpath = /var/log/sogo/sogo.log + + +# +# FTP servers +# + +[vsftpd] + +enabled = false +port = ftp,ftp-data,ftps,ftps-data +filter = vsftpd +logpath = /var/log/vsftpd.log +# or overwrite it in jails.local to be +# logpath = /var/log/auth.log +# if you want to rely on PAM failed login attempts +# vsftpd's failregex should match both of those formats +maxretry = 6 + + +[proftpd] + +enabled = false +port = ftp,ftp-data,ftps,ftps-data +filter = proftpd +logpath = /var/log/proftpd/proftpd.log +maxretry = 6 + + +[pure-ftpd] + +enabled = false +port = ftp,ftp-data,ftps,ftps-data +filter = pure-ftpd +logpath = /var/log/syslog +maxretry = 6 + + +[wuftpd] + +enabled = false +port = ftp,ftp-data,ftps,ftps-data +filter = wuftpd +logpath = /var/log/syslog +maxretry = 6 + + +# +# Mail servers +# + +[postfix] + +enabled = false +port = smtp,ssmtp,submission +filter = postfix +logpath = /var/log/mail.log + + +[couriersmtp] + +enabled = false +port = smtp,ssmtp,submission +filter = couriersmtp +logpath = /var/log/mail.log + + +# +# Mail servers authenticators: might be used for smtp,ftp,imap servers, so +# all relevant ports get banned +# + +[courierauth] + +enabled = false +port = smtp,ssmtp,submission,imap2,imap3,imaps,pop3,pop3s +filter = courierlogin +logpath = /var/log/mail.log + + +[sasl] + +enabled = false +port = smtp,ssmtp,submission,imap2,imap3,imaps,pop3,pop3s +filter = postfix-sasl +# You might consider monitoring /var/log/mail.warn instead if you are +# running postfix since it would provide the same log lines at the +# "warn" level but overall at the smaller filesize. +logpath = /var/log/mail.log + +[dovecot] + +enabled = false +port = smtp,ssmtp,submission,imap2,imap3,imaps,pop3,pop3s +filter = dovecot +logpath = /var/log/mail.log + +# To log wrong MySQL access attempts add to /etc/my.cnf: +# log-error=/var/log/mysqld.log +# log-warning = 2 +[mysqld-auth] + +enabled = false +filter = mysqld-auth +port = 3306 +logpath = /var/log/mysqld.log + + +# DNS Servers + + +# These jails block attacks against named (bind9). By default, logging is off +# with bind9 installation. You will need something like this: +# +# logging { +# channel security_file { +# file "/var/log/named/security.log" versions 3 size 30m; +# severity dynamic; +# print-time yes; +# }; +# category security { +# security_file; +# }; +# }; +# +# in your named.conf to provide proper logging + +# !!! WARNING !!! +# Since UDP is connection-less protocol, spoofing of IP and imitation +# of illegal actions is way too simple. Thus enabling of this filter +# might provide an easy way for implementing a DoS against a chosen +# victim. See +# http://nion.modprobe.de/blog/archives/690-fail2ban-+-dns-fail.html +# Please DO NOT USE this jail unless you know what you are doing. +#[named-refused-udp] +# +#enabled = false +#port = domain,953 +#protocol = udp +#filter = named-refused +#logpath = /var/log/named/security.log + +[named-refused-tcp] + +enabled = false +port = domain,953 +protocol = tcp +filter = named-refused +logpath = /var/log/named/security.log + +# Multiple jails, 1 per protocol, are necessary ATM: +# see https://github.com/fail2ban/fail2ban/issues/37 +[asterisk-tcp] + +enabled = false +filter = asterisk +port = 5060,5061 +protocol = tcp +logpath = /var/log/asterisk/messages + +[asterisk-udp] + +enabled = false +filter = asterisk +port = 5060,5061 +protocol = udp +logpath = /var/log/asterisk/messages - # Turning off services we don't need the jumpbox running - service nginx stop - service php7.0-fpm stop - service varnish stop - service varnishncsa stop - service varnishlog stop - # make sure Moodle can read its code directory but not write - sudo chown -R root.root /moodle/html/moodle - sudo find /moodle/html/moodle -type f -exec chmod 644 '{}' \; - sudo find /moodle/html/moodle -type d -exec chmod 755 '{}' \; +# Jail for more extended banning of persistent abusers +# !!! WARNING !!! +# Make sure that your loglevel specified in fail2ban.conf/.local +# is not at DEBUG level -- which might then cause fail2ban to fall into +# an infinite loop constantly feeding itself with non-informative lines +[recidive] -} > /tmp/install.log +enabled = false +filter = recidive +logpath = /var/log/fail2ban.log +action = iptables-allports[name=recidive] + sendmail-whois-lines[name=recidive, logpath=/var/log/fail2ban.log] +bantime = 604800 ; 1 week +findtime = 86400 ; 1 day +maxretry = 5 +EOF +} diff --git a/scripts/install_elastic.sh b/scripts/install_elastic.sh index e4ebba1d..999a44cc 100644 --- a/scripts/install_elastic.sh +++ b/scripts/install_elastic.sh @@ -1,6 +1,5 @@ -# Custom Script for Linux - #!/bin/bash +# Custom Script for Linux # The MIT License (MIT) # diff --git a/scripts/install_gluster.sh b/scripts/install_gluster.sh index a3065974..dbb229af 100644 --- a/scripts/install_gluster.sh +++ b/scripts/install_gluster.sh @@ -40,8 +40,8 @@ sudo apt-get -y install unattended-upgrades { check_os() { - grep ubuntu /proc/version > /dev/null 2>&1 - isubuntu=${?} + grep -q -s ubuntu /proc/version && _RET=$? || _RET=$? + isubuntu=$_RET } scan_for_new_disks() { @@ -70,8 +70,8 @@ sudo apt-get -y install unattended-upgrades } create_raid0_ubuntu() { - dpkg -s mdadm - if [ ${?} -eq 1 ]; + dpkg -s mdadm && _RET=$? || _RET=$? + if [ $_RET -eq 1 ]; then echo "installing mdadm" sudo apt-get -y -q install mdadm @@ -106,8 +106,8 @@ sudo apt-get -y install unattended-upgrades add_to_fstab() { UUID=${1} MOUNTPOINT=${2} - grep "${UUID}" /etc/fstab >/dev/null 2>&1 - if [ ${?} -eq 0 ]; + grep -q -s "${UUID}" /etc/fstab && _RET=$? || _RET=$? + if [ $_RET -eq 0 ]; then echo "Not adding ${UUID} to fstab again (it's already there!)" else @@ -117,8 +117,8 @@ sudo apt-get -y install unattended-upgrades } configure_disks() { - ls "${MOUNTPOINT}" - if [ ${?} -eq 0 ] + ls "${MOUNTPOINT}" && _RET=$? || _RET=$? + if [ $_RET -eq 0 ] then return fi @@ -178,8 +178,8 @@ sudo apt-get -y install unattended-upgrades } install_glusterfs_ubuntu() { - dpkg -l | grep glusterfs - if [ ${?} -eq 0 ]; + dpkg -l | grep glusterfs && _RET=$? || _RET=$? + if [ $_RET -eq 0 ]; then return fi @@ -188,7 +188,7 @@ sudo apt-get -y install unattended-upgrades then echo "adding gluster ppa" apt-get -y install python-software-properties - apt-add-repository -y ppa:gluster/glusterfs-3.8 + apt-add-repository -y ppa:gluster/glusterfs-3.10 apt-get -y update fi @@ -203,8 +203,8 @@ sudo apt-get -y install unattended-upgrades if [ $isubuntu -eq 0 ]; then - /etc/init.d/glusterfs-server status - if [ ${?} -ne 0 ]; + /etc/init.d/glusterfs-server status && _RET=$? || _RET=$? + if [ $_RET -ne 0 ]; then install_glusterfs_ubuntu fi @@ -213,9 +213,9 @@ sudo apt-get -y install unattended-upgrades echo "gluster step2" GLUSTERDIR="${MOUNTPOINT}/brick" - ls "${GLUSTERDIR}" + ls "${GLUSTERDIR}" && _RET=$? || _RET=$? - if [ ${?} -ne 0 ]; + if [ $_RET -ne 0 ]; then mkdir "${GLUSTERDIR}" fi @@ -240,17 +240,17 @@ sudo apt-get -y install unattended-upgrades echo $glustervm ping -c 3 $glustervm - gluster peer probe $glustervm - if [ ${?} -ne 0 ]; + gluster peer probe $glustervm && _RET=$? || _RET=$? + if [ $_RET -ne 0 ]; then failed=1 echo "gluster peer probe $glustervm failed" fi gluster peer status - gluster peer status | grep $glustervm + gluster peer status | grep $glustervm && _RET=$? || _RET=$? - if [ ${?} -ne 0 ]; + if [ $_RET -ne 0 ]; then failed=1 echo "gluster peer status $glustervm failed" diff --git a/scripts/install_moodle.sh b/scripts/install_moodle.sh new file mode 100644 index 00000000..8888806d --- /dev/null +++ b/scripts/install_moodle.sh @@ -0,0 +1,963 @@ +#!/bin/bash + +# The MIT License (MIT) +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +set -ex + +#parameters +{ + moodle_on_azure_configs_json_path=${1} + + . ./helper_functions.sh + + get_setup_params_from_configs_json $moodle_on_azure_configs_json_path || exit 99 + + echo $moodleVersion >> /tmp/vars.txt + echo $glusterNode >> /tmp/vars.txt + echo $glusterVolume >> /tmp/vars.txt + echo $siteFQDN >> /tmp/vars.txt + echo $httpsTermination >> /tmp/vars.txt + echo $dbIP >> /tmp/vars.txt + echo $moodledbname >> /tmp/vars.txt + echo $moodledbuser >> /tmp/vars.txt + echo $moodledbpass >> /tmp/vars.txt + echo $adminpass >> /tmp/vars.txt + echo $dbadminlogin >> /tmp/vars.txt + echo $dbadminloginazure >> /tmp/vars.txt + echo $dbadminpass >> /tmp/vars.txt + echo $storageAccountName >> /tmp/vars.txt + echo $storageAccountKey >> /tmp/vars.txt + echo $azuremoodledbuser >> /tmp/vars.txt + echo $redisDns >> /tmp/vars.txt + echo $redisAuth >> /tmp/vars.txt + echo $elasticVm1IP >> /tmp/vars.txt + echo $installO365pluginsSwitch >> /tmp/vars.txt + echo $dbServerType >> /tmp/vars.txt + echo $fileServerType >> /tmp/vars.txt + echo $mssqlDbServiceObjectiveName >> /tmp/vars.txt + echo $mssqlDbEdition >> /tmp/vars.txt + echo $mssqlDbSize >> /tmp/vars.txt + echo $installObjectFsSwitch >> /tmp/vars.txt + echo $installGdprPluginsSwitch >> /tmp/vars.txt + echo $thumbprintSslCert >> /tmp/vars.txt + echo $thumbprintCaCert >> /tmp/vars.txt + echo $searchType >> /tmp/vars.txt + echo $azureSearchKey >> /tmp/vars.txt + echo $azureSearchNameHost >> /tmp/vars.txt + echo $tikaVmIP >> /tmp/vars.txt + echo $nfsByoIpExportPath >> /tmp/vars.txt + echo $storageAccountType >>/tmp/vars.txt + echo $fileServerDiskSize >>/tmp/vars.txt + echo $phpVersion >> /tmp/vars.txt + + check_fileServerType_param $fileServerType + + #Updating php sources + sudo add-apt-repository ppa:ondrej/php -y + sudo apt-get update + + if [ "$dbServerType" = "mysql" ]; then + mysqlIP=$dbIP + mysqladminlogin=$dbadminloginazure + mysqladminpass=$dbadminpass + elif [ "$dbServerType" = "mssql" ]; then + mssqlIP=$dbIP + mssqladminlogin=$dbadminloginazure + mssqladminpass=$dbadminpass + + elif [ "$dbServerType" = "postgres" ]; then + postgresIP=$dbIP + pgadminlogin=$dbadminloginazure + pgadminpass=$dbadminpass + else + echo "Invalid dbServerType ($dbServerType) given. Only 'mysql' or 'postgres' or 'mssql' is allowed. Exiting" + exit 1 + fi + + # make sure system does automatic updates and fail2ban + sudo apt-get -y update + sudo apt-get -y install unattended-upgrades fail2ban + + config_fail2ban + + # create gluster, nfs or Azure Files mount point + mkdir -p /moodle + + export DEBIAN_FRONTEND=noninteractive + + if [ $fileServerType = "gluster" ]; then + # configure gluster repository & install gluster client + sudo add-apt-repository ppa:gluster/glusterfs-3.10 -y >> /tmp/apt1.log + elif [ $fileServerType = "nfs" ]; then + # configure NFS server and export + setup_raid_disk_and_filesystem /moodle /dev/md1 /dev/md1p1 + configure_nfs_server_and_export /moodle + fi + + sudo apt-get -y update >> /tmp/apt2.log + sudo apt-get -y --force-yes install rsyslog git >> /tmp/apt3.log + + if [ $fileServerType = "gluster" ]; then + sudo apt-get -y --force-yes install glusterfs-client >> /tmp/apt3.log + elif [ "$fileServerType" = "azurefiles" ]; then + sudo apt-get -y --force-yes install cifs-utils >> /tmp/apt3.log + fi + + if [ $dbServerType = "mysql" ]; then + sudo apt-get -y --force-yes install mysql-client >> /tmp/apt3.log + elif [ "$dbServerType" = "postgres" ]; then + sudo apt-get -y --force-yes install postgresql-client >> /tmp/apt3.log + fi + + if [ "$installObjectFsSwitch" = "true" -o "$fileServerType" = "azurefiles" ]; then + # install azure cli & setup container + echo "deb [arch=amd64] https://packages.microsoft.com/repos/azure-cli/ wheezy main" | \ + sudo tee /etc/apt/sources.list.d/azure-cli.list + curl -L https://packages.microsoft.com/keys/microsoft.asc | sudo apt-key add - >> /tmp/apt4.log + sudo apt-get -y install apt-transport-https >> /tmp/apt4.log + sudo apt-get -y update > /dev/null + sudo apt-get -y install azure-cli >> /tmp/apt4.log + + # FileStorage accounts can only be used to store Azure file shares; + # Premium_LRS will support FileStorage kind + # No other storage resources (blob containers, queues, tables, etc.) can be deployed in a FileStorage account. + if [ $storageAccountType != "Premium_LRS" ]; then + az storage container create \ + --name objectfs \ + --account-name $storageAccountName \ + --account-key $storageAccountKey \ + --public-access off \ + --fail-on-exist >> /tmp/wabs.log + + az storage container policy create \ + --account-name $storageAccountName \ + --account-key $storageAccountKey \ + --container-name objectfs \ + --name readwrite \ + --start $(date --date="1 day ago" +%F) \ + --expiry $(date --date="2199-01-01" +%F) \ + --permissions rw >> /tmp/wabs.log + + sas=$(az storage container generate-sas \ + --account-name $storageAccountName \ + --account-key $storageAccountKey \ + --name objectfs \ + --policy readwrite \ + --output tsv) + fi + fi + + if [ $fileServerType = "gluster" ]; then + # mount gluster files system + echo -e '\n\rInstalling GlusterFS on '$glusterNode':/'$glusterVolume '/moodle\n\r' + setup_and_mount_gluster_moodle_share $glusterNode $glusterVolume + elif [ $fileServerType = "nfs-ha" ]; then + # mount NFS-HA export + echo -e '\n\rMounting NFS export from '$nfsHaLbIP' on /moodle\n\r' + configure_nfs_client_and_mount $nfsHaLbIP $nfsHaExportPath /moodle + elif [ $fileServerType = "nfs-byo" ]; then + # mount NFS-BYO export + echo -e '\n\rMounting NFS export from '$nfsByoIpExportPath' on /moodle\n\r' + configure_nfs_client_and_mount0 $nfsByoIpExportPath /moodle + fi + + # install pre-requisites + sudo add-apt-repository ppa:ubuntu-toolchain-r/ppa + sudo apt-get -y update > /dev/null 2>&1 + # sudo apt-get install -y --fix-missing python-software-properties unzip + sudo apt-get -y install software-properties-common + sudo apt-get -y install unzip + + + # install the entire stack + # passing php versions $phpVersion + sudo apt-get -y --force-yes install nginx php$phpVersion-fpm varnish >> /tmp/apt5a.log + sudo apt-get -y --force-yes install php$phpVersion php$phpVersion-cli php$phpVersion-curl php$phpVersion-zip >> /tmp/apt5b.log + + # Moodle requirements + sudo apt-get -y update > /dev/null + sudo apt-get install -y --force-yes graphviz aspell php$phpVersion-common php$phpVersion-soap php$phpVersion-json php$phpVersion-redis > /tmp/apt6.log + sudo apt-get install -y --force-yes php$phpVersion-bcmath php$phpVersion-gd php$phpVersion-xmlrpc php$phpVersion-intl php$phpVersion-xml php$phpVersion-bz2 php-pear php$phpVersion-mbstring php$phpVersion-dev mcrypt >> /tmp/apt6.log + PhpVer=$(get_php_version) + if [ $dbServerType = "mysql" ]; then + sudo apt-get install -y --force-yes php$phpVersion-mysql + elif [ $dbServerType = "mssql" ]; then + sudo apt-get install -y libapache2-mod-php # Need this because install_php_mssql_driver tries to update apache2-mod-php settings always (which will fail without this) + install_php_mssql_driver + else + sudo apt-get install -y --force-yes php-pgsql + fi + + # Set up initial moodle dirs + mkdir -p /moodle/html + mkdir -p /moodle/certs + mkdir -p /moodle/moodledata + + o365pluginVersion=$(get_o365plugin_version_from_moodle_version $moodleVersion) + moodleStableVersion=$o365pluginVersion # Need Moodle stable version for GDPR plugins, and o365pluginVersion is just Moodle stable version, so reuse it. + moodleUnzipDir=$(get_moodle_unzip_dir_from_moodle_version $moodleVersion) + + # install Moodle + echo '#!/bin/bash + mkdir -p /moodle/tmp + cd /moodle/tmp + + if [ ! -d /moodle/html/moodle ]; then + # 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. + /usr/bin/curl -k --max-redirs 10 https://github.com/moodle/moodle/archive/'$moodleVersion'.zip -L -o moodle.zip + /usr/bin/unzip -q moodle.zip + /bin/mv '$moodleUnzipDir' /moodle/html/moodle + fi + + if [ "'$installGdprPluginsSwitch'" = "true" ]; then + # 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) + curl -k --max-redirs 10 https://github.com/moodlehq/moodle-tool_policy/archive/'$moodleStableVersion'.zip -L -o plugin-policy.zip + unzip -q plugin-policy.zip + mv moodle-tool_policy-'$moodleStableVersion' /moodle/html/moodle/admin/tool/policy + + curl -k --max-redirs 10 https://github.com/moodlehq/moodle-tool_dataprivacy/archive/'$moodleStableVersion'.zip -L -o plugin-dataprivacy.zip + unzip -q plugin-dataprivacy.zip + mv moodle-tool_dataprivacy-'$moodleStableVersion' /moodle/html/moodle/admin/tool/dataprivacy + fi + + if [ "'$installO365pluginsSwitch'" = "true" ]; then + # install Office 365 plugins + curl -k --max-redirs 10 https://github.com/Microsoft/o365-moodle/archive/'$o365pluginVersion'.zip -L -o o365.zip + unzip -q o365.zip + cp -r o365-moodle-'$o365pluginVersion'/* /moodle/html/moodle + rm -rf o365-moodle-'$o365pluginVersion' + fi + + if [ "'$searchType'" = "elastic" ]; then + # Install ElasticSearch plugin + /usr/bin/curl -k --max-redirs 10 https://github.com/catalyst/moodle-search_elastic/archive/master.zip -L -o plugin-elastic.zip + /usr/bin/unzip -q plugin-elastic.zip + /bin/mv moodle-search_elastic-master /moodle/html/moodle/search/engine/elastic + + # Install ElasticSearch plugin dependency + /usr/bin/curl -k --max-redirs 10 https://github.com/catalyst/moodle-local_aws/archive/master.zip -L -o local-aws.zip + /usr/bin/unzip -q local-aws.zip + /bin/mv moodle-local_aws-master /moodle/html/moodle/local/aws + + elif [ "'$searchType'" = "azure" ]; then + # Install Azure Search service plugin + /usr/bin/curl -k --max-redirs 10 https://github.com/catalyst/moodle-search_azure/archive/master.zip -L -o plugin-azure-search.zip + /usr/bin/unzip -q plugin-azure-search.zip + /bin/mv moodle-search_azure-master /moodle/html/moodle/search/engine/azure + fi + + if [ "'$installObjectFsSwitch'" = "true" ]; then + # Install the ObjectFS plugin + /usr/bin/curl -k --max-redirs 10 https://github.com/catalyst/moodle-tool_objectfs/archive/master.zip -L -o plugin-objectfs.zip + /usr/bin/unzip -q plugin-objectfs.zip + /bin/mv moodle-tool_objectfs-master /moodle/html/moodle/admin/tool/objectfs + + # Install the ObjectFS Azure library + /usr/bin/curl -k --max-redirs 10 https://github.com/catalyst/moodle-local_azure_storage/archive/master.zip -L -o plugin-azurelibrary.zip + /usr/bin/unzip -q plugin-azurelibrary.zip + /bin/mv moodle-local_azure_storage-master /moodle/html/moodle/local/azure_storage + fi + cd /moodle + rm -rf /moodle/tmp + ' > /tmp/setup-moodle.sh + + chmod 755 /tmp/setup-moodle.sh + /tmp/setup-moodle.sh >> /tmp/setupmoodle.log + + # Build nginx config + cat < /etc/nginx/nginx.conf +user www-data; +worker_processes 2; +pid /run/nginx.pid; + +events { + worker_connections 768; +} + +http { + + sendfile on; + tcp_nopush on; + tcp_nodelay on; + keepalive_timeout 65; + types_hash_max_size 2048; + client_max_body_size 0; + proxy_max_temp_file_size 0; + server_names_hash_bucket_size 128; + fastcgi_buffers 16 16k; + fastcgi_buffer_size 32k; + proxy_buffering off; + include /etc/nginx/mime.types; + default_type application/octet-stream; + + access_log /var/log/nginx/access.log; + error_log /var/log/nginx/error.log; + + set_real_ip_from 127.0.0.1; + real_ip_header X-Forwarded-For; + #upgrading to TLSv1.2 and droping 1 & 1.1 + ssl_protocols TLSv1.2; + #ssl_prefer_server_ciphers on; + #adding ssl ciphers + 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; + + + gzip on; + gzip_disable "msie6"; + gzip_vary on; + gzip_proxied any; + gzip_comp_level 6; + gzip_buffers 16 8k; + gzip_http_version 1.1; + gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript; +EOF + + if [ "$httpsTermination" != "None" ]; then + cat <> /etc/nginx/nginx.conf + map \$http_x_forwarded_proto \$fastcgi_https { + default \$https; + http ''; + https on; + } +EOF + fi + + cat <> /etc/nginx/nginx.conf + log_format moodle_combined '\$remote_addr - \$upstream_http_x_moodleuser [\$time_local] ' + '"\$request" \$status \$body_bytes_sent ' + '"\$http_referer" "\$http_user_agent"'; + + + include /etc/nginx/conf.d/*.conf; + include /etc/nginx/sites-enabled/*; +} +EOF + + cat <> /etc/nginx/sites-enabled/${siteFQDN}.conf +server { + listen 81 default; + server_name ${siteFQDN}; + root /moodle/html/moodle; + index index.php index.html index.htm; + + # Log to syslog + error_log syslog:server=localhost,facility=local1,severity=error,tag=moodle; + access_log syslog:server=localhost,facility=local1,severity=notice,tag=moodle moodle_combined; + + # Log XFF IP instead of varnish + set_real_ip_from 10.0.0.0/8; + set_real_ip_from 127.0.0.1; + set_real_ip_from 172.16.0.0/12; + set_real_ip_from 192.168.0.0/16; + real_ip_header X-Forwarded-For; + real_ip_recursive on; +EOF + if [ "$httpsTermination" != "None" ]; then + cat <> /etc/nginx/sites-enabled/${siteFQDN}.conf + # Redirect to https + if (\$http_x_forwarded_proto != https) { + return 301 https://\$server_name\$request_uri; + } + rewrite ^/(.*\.php)(/)(.*)$ /\$1?file=/\$3 last; +EOF + fi + + cat <> /etc/nginx/sites-enabled/${siteFQDN}.conf + # Filter out php-fpm status page + location ~ ^/server-status { + return 404; + } + + location / { + try_files \$uri \$uri/index.php?\$query_string; + } + + location ~ [^/]\.php(/|$) { + fastcgi_split_path_info ^(.+?\.php)(/.*)$; + if (!-f \$document_root\$fastcgi_script_name) { + return 404; + } + + fastcgi_buffers 16 16k; + fastcgi_buffer_size 32k; + fastcgi_param SCRIPT_FILENAME \$document_root\$fastcgi_script_name; + fastcgi_pass unix:/run/php/php${PhpVer}-fpm.sock; + fastcgi_read_timeout 3600; + fastcgi_index index.php; + include fastcgi_params; + } +} +EOF + if [ "$httpsTermination" = "VMSS" ]; then + cat <> /etc/nginx/sites-enabled/${siteFQDN}.conf +server { + listen 443 ssl; + root /moodle/html/moodle; + index index.php index.html index.htm; + + ssl on; + ssl_certificate /moodle/certs/nginx.crt; + ssl_certificate_key /moodle/certs/nginx.key; + + # Log to syslog + error_log syslog:server=localhost,facility=local1,severity=error,tag=moodle; + access_log syslog:server=localhost,facility=local1,severity=notice,tag=moodle moodle_combined; + + # Log XFF IP instead of varnish + set_real_ip_from 10.0.0.0/8; + set_real_ip_from 127.0.0.1; + set_real_ip_from 172.16.0.0/12; + set_real_ip_from 192.168.0.0/16; + real_ip_header X-Forwarded-For; + real_ip_recursive on; + + location / { + proxy_set_header Host \$host; + proxy_set_header HTTP_REFERER \$http_referer; + proxy_set_header X-Forwarded-Host \$host; + proxy_set_header X-Forwarded-Server \$host; + proxy_set_header X-Forwarded-Proto https; + proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for; + proxy_pass http://localhost:80; + } +} +EOF + fi + + if [ "$httpsTermination" = "VMSS" ]; then + ### SSL cert ### + if [ "$thumbprintSslCert" != "None" ]; then + echo "Using VM's cert (/var/lib/waagent/$thumbprintSslCert.*) for SSL..." + cat /var/lib/waagent/$thumbprintSslCert.prv > /moodle/certs/nginx.key + cat /var/lib/waagent/$thumbprintSslCert.crt > /moodle/certs/nginx.crt + if [ "$thumbprintCaCert" != "None" ]; then + echo "CA cert was specified (/var/lib/waagent/$thumbprintCaCert.crt), so append it to nginx.crt..." + cat /var/lib/waagent/$thumbprintCaCert.crt >> /moodle/certs/nginx.crt + fi + else + echo -e "Generating SSL self-signed certificate" + 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" + fi + chown www-data:www-data /moodle/certs/nginx.* + chmod 0400 /moodle/certs/nginx.* + fi + + # php config + PhpVer=$(get_php_version) + PhpIni=/etc/php/${PhpVer}/fpm/php.ini + sed -i "s/memory_limit.*/memory_limit = 512M/" $PhpIni + sed -i "s/max_execution_time.*/max_execution_time = 18000/" $PhpIni + sed -i "s/max_input_vars.*/max_input_vars = 100000/" $PhpIni + sed -i "s/max_input_time.*/max_input_time = 600/" $PhpIni + sed -i "s/upload_max_filesize.*/upload_max_filesize = 1024M/" $PhpIni + sed -i "s/post_max_size.*/post_max_size = 1056M/" $PhpIni + sed -i "s/;opcache.use_cwd.*/opcache.use_cwd = 1/" $PhpIni + sed -i "s/;opcache.validate_timestamps.*/opcache.validate_timestamps = 1/" $PhpIni + sed -i "s/;opcache.save_comments.*/opcache.save_comments = 1/" $PhpIni + sed -i "s/;opcache.enable_file_override.*/opcache.enable_file_override = 0/" $PhpIni + sed -i "s/;opcache.enable.*/opcache.enable = 1/" $PhpIni + sed -i "s/;opcache.memory_consumption.*/opcache.memory_consumption = 256/" $PhpIni + sed -i "s/;opcache.max_accelerated_files.*/opcache.max_accelerated_files = 8000/" $PhpIni + + # fpm config - overload this + cat < /etc/php/${PhpVer}/fpm/pool.d/www.conf +[www] +user = www-data +group = www-data +listen = /run/php/php${PhpVer}-fpm.sock +listen.owner = www-data +listen.group = www-data +pm = dynamic +pm.max_children = 3000 +pm.start_servers = 20 +pm.min_spare_servers = 22 +pm.max_spare_servers = 30 +EOF + + # Remove the default site. Moodle is the only site we want + rm -f /etc/nginx/sites-enabled/default + + # restart Nginx + sudo service nginx restart + + # Configure varnish startup for 16.04 + 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" + sed -i "s/^ExecStart.*/${VARNISHSTART}/" /lib/systemd/system/varnish.service + + # Configure varnish VCL for moodle + cat <> /etc/varnish/moodle.vcl +vcl 4.0; + +import std; +import directors; +backend default { + .host = "localhost"; + .port = "81"; + .first_byte_timeout = 3600s; + .connect_timeout = 600s; + .between_bytes_timeout = 600s; +} + +sub vcl_recv { + # Varnish does not support SPDY or HTTP/2.0 untill we upgrade to Varnish 5.0 + if (req.method == "PRI") { + return (synth(405)); + } + + if (req.restarts == 0) { + if (req.http.X-Forwarded-For) { + set req.http.X-Forwarded-For = req.http.X-Forwarded-For + ", " + client.ip; + } else { + set req.http.X-Forwarded-For = client.ip; + } + } + + # Non-RFC2616 or CONNECT HTTP requests methods filtered. Pipe requests directly to backend + if (req.method != "GET" && + req.method != "HEAD" && + req.method != "PUT" && + req.method != "POST" && + req.method != "TRACE" && + req.method != "OPTIONS" && + req.method != "DELETE") { + return (pipe); + } + + # Varnish don't mess with healthchecks + if (req.url ~ "^/admin/tool/heartbeat" || req.url ~ "^/healthcheck.php") + { + return (pass); + } + + # Pipe requests to backup.php straight to backend - prevents problem with progress bar long polling 503 problem + # This is here because backup.php is POSTing to itself - Filter before !GET&&!HEAD + if (req.url ~ "^/backup/backup.php") + { + return (pipe); + } + + # Varnish only deals with GET and HEAD by default. If request method is not GET or HEAD, pass request to backend + if (req.method != "GET" && req.method != "HEAD") { + return (pass); + } + + ### Rules for Moodle and Totara sites ### + # Moodle doesn't require Cookie to serve following assets. Remove Cookie header from request, so it will be looked up. + if ( req.url ~ "^/altlogin/.+/.+\.(png|jpg|jpeg|gif|css|js|webp)$" || + req.url ~ "^/pix/.+\.(png|jpg|jpeg|gif)$" || + req.url ~ "^/theme/font.php" || + req.url ~ "^/theme/image.php" || + req.url ~ "^/theme/javascript.php" || + req.url ~ "^/theme/jquery.php" || + req.url ~ "^/theme/styles.php" || + req.url ~ "^/theme/yui" || + req.url ~ "^/lib/javascript.php/-1/" || + req.url ~ "^/lib/requirejs.php/-1/" + ) + { + set req.http.X-Long-TTL = "86400"; + unset req.http.Cookie; + return(hash); + } + + # Perform lookup for selected assets that we know are static but Moodle still needs a Cookie + if( req.url ~ "^/theme/.+\.(png|jpg|jpeg|gif|css|js|webp)" || + req.url ~ "^/lib/.+\.(png|jpg|jpeg|gif|css|js|webp)" || + req.url ~ "^/pluginfile.php/[0-9]+/course/overviewfiles/.+\.(?i)(png|jpg)$" + ) + { + # Set internal temporary header, based on which we will do things in vcl_backend_response + set req.http.X-Long-TTL = "86400"; + return (hash); + } + + # Serve requests to SCORM checknet.txt from varnish. Have to remove get parameters. Response body always contains "1" + if ( req.url ~ "^/lib/yui/build/moodle-core-checknet/assets/checknet.txt" ) + { + set req.url = regsub(req.url, "(.*)\?.*", "\1"); + unset req.http.Cookie; # Will go to hash anyway at the end of vcl_recv + set req.http.X-Long-TTL = "86400"; + return(hash); + } + + # Requests containing "Cookie" or "Authorization" headers will not be cached + if (req.http.Authorization || req.http.Cookie) { + return (pass); + } + + # Almost everything in Moodle correctly serves Cache-Control headers, if + # needed, which varnish will honor, but there are some which don't. Rather + # than explicitly finding them all and listing them here we just fail safe + # and don't cache unknown urls that get this far. + return (pass); +} + +sub vcl_backend_response { + # Happens after we have read the response headers from the backend. + # + # Here you clean the response headers, removing silly Set-Cookie headers + # and other mistakes your backend does. + + # We know these assest are static, let's set TTL >0 and allow client caching + 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 ) + { # If max-age < defined in X-Long-TTL header + set beresp.http.X-Orig-Pragma = beresp.http.Pragma; unset beresp.http.Pragma; + set beresp.http.X-Orig-Cache-Control = beresp.http.Cache-Control; + set beresp.http.Cache-Control = "public, max-age="+bereq.http.X-Long-TTL+", no-transform"; + set beresp.ttl = std.duration(bereq.http.X-Long-TTL + "s", 1s); + unset bereq.http.X-Long-TTL; + } + else if( !beresp.http.Cache-Control && bereq.http.X-Long-TTL && !beresp.http.WWW-Authenticate ) { + set beresp.http.X-Orig-Pragma = beresp.http.Pragma; unset beresp.http.Pragma; + set beresp.http.Cache-Control = "public, max-age="+bereq.http.X-Long-TTL+", no-transform"; + set beresp.ttl = std.duration(bereq.http.X-Long-TTL + "s", 1s); + unset bereq.http.X-Long-TTL; + } + else { # Don't touch headers if max-age > defined in X-Long-TTL header + unset bereq.http.X-Long-TTL; + } + + # Here we set X-Trace header, prepending it to X-Trace header received from backend. Useful for troubleshooting + if(beresp.http.x-trace && !beresp.was_304) { + 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; + } + else { + set beresp.http.X-Trace = regsub(server.identity, "^([^.]+),?.*$", "\1")+"->"+regsub(beresp.backend.name, "^(.+)\((?:[0-9]{1,3}\.){3}([0-9]{1,3})\)","\1(\2)"); + } + + # Gzip JS, CSS is done at the ngnix level doing it here dosen't respect the no buffer requsets + # if (beresp.http.content-type ~ "application/javascript.*" || beresp.http.content-type ~ "text") { + # set beresp.do_gzip = true; + #} +} + +sub vcl_deliver { + + # Revert back to original Cache-Control header before delivery to client + if (resp.http.X-Orig-Cache-Control) + { + set resp.http.Cache-Control = resp.http.X-Orig-Cache-Control; + unset resp.http.X-Orig-Cache-Control; + } + + # Revert back to original Pragma header before delivery to client + if (resp.http.X-Orig-Pragma) + { + set resp.http.Pragma = resp.http.X-Orig-Pragma; + unset resp.http.X-Orig-Pragma; + } + + # (Optional) X-Cache HTTP header will be added to responce, indicating whether object was retrieved from backend, or served from cache + if (obj.hits > 0) { + set resp.http.X-Cache = "HIT"; + } else { + set resp.http.X-Cache = "MISS"; + } + + # Set X-AuthOK header when totara/varnsih authentication succeeded + if (req.http.X-AuthOK) { + set resp.http.X-AuthOK = req.http.X-AuthOK; + } + + # If desired "Via: 1.1 Varnish-v4" response header can be removed from response + unset resp.http.Via; + unset resp.http.Server; + + return(deliver); +} + +sub vcl_backend_error { + # More comprehensive varnish error page. Display time, instance hostname, host header, url for easier troubleshooting. + set beresp.http.Content-Type = "text/html; charset=utf-8"; + set beresp.http.Retry-After = "5"; + synthetic( {" + + + + "} + beresp.status + " " + beresp.reason + {" + + +

Error "} + beresp.status + " " + beresp.reason + {"

+

"} + beresp.reason + {"

+

Guru Meditation:

+

Time: "} + now + {"

+

Node: "} + server.hostname + {"

+

Host: "} + bereq.http.host + {"

+

URL: "} + bereq.url + {"

+

XID: "} + bereq.xid + {"

+
+

Varnish cache server + + + "} ); + return (deliver); +} + +sub vcl_synth { + + #Redirect using '301 - Permanent Redirect', permanent redirect + if (resp.status == 851) { + set resp.http.Location = req.http.x-redir; + set resp.http.X-Varnish-Redirect = true; + set resp.status = 301; + return (deliver); + } + + #Redirect using '302 - Found', temporary redirect + if (resp.status == 852) { + set resp.http.Location = req.http.x-redir; + set resp.http.X-Varnish-Redirect = true; + set resp.status = 302; + return (deliver); + } + + #Redirect using '307 - Temporary Redirect', !GET&&!HEAD requests, dont change method on redirected requests + if (resp.status == 857) { + set resp.http.Location = req.http.x-redir; + set resp.http.X-Varnish-Redirect = true; + set resp.status = 307; + return (deliver); + } + + #Respond with 403 - Forbidden + if (resp.status == 863) { + set resp.http.X-Varnish-Error = true; + set resp.status = 403; + return (deliver); + } +} +EOF + + # Restart Varnish + systemctl daemon-reload + service varnish restart + + if [ $dbServerType = "mysql" ]; then + mysql -h $mysqlIP -u $mysqladminlogin -p${mysqladminpass} -e "CREATE DATABASE ${moodledbname} CHARACTER SET utf8;" + mysql -h $mysqlIP -u $mysqladminlogin -p${mysqladminpass} -e "GRANT ALL ON ${moodledbname}.* TO ${moodledbuser} IDENTIFIED BY '${moodledbpass}';" + + echo "mysql -h $mysqlIP -u $mysqladminlogin -p${mysqladminpass} -e \"CREATE DATABASE ${moodledbname};\"" >> /tmp/debug + echo "mysql -h $mysqlIP -u $mysqladminlogin -p${mysqladminpass} -e \"GRANT ALL ON ${moodledbname}.* TO ${moodledbuser} IDENTIFIED BY '${moodledbpass}';\"" >> /tmp/debug + elif [ $dbServerType = "mssql" ]; then + /opt/mssql-tools/bin/sqlcmd -S $mssqlIP -U $mssqladminlogin -P ${mssqladminpass} -Q "CREATE DATABASE ${moodledbname} ( MAXSIZE = $mssqlDbSize, EDITION = '$mssqlDbEdition', SERVICE_OBJECTIVE = '$mssqlDbServiceObjectiveName' )" + /opt/mssql-tools/bin/sqlcmd -S $mssqlIP -U $mssqladminlogin -P ${mssqladminpass} -Q "CREATE LOGIN ${moodledbuser} with password = '${moodledbpass}'" + /opt/mssql-tools/bin/sqlcmd -S $mssqlIP -U $mssqladminlogin -P ${mssqladminpass} -d ${moodledbname} -Q "CREATE USER ${moodledbuser} FROM LOGIN ${moodledbuser}" + /opt/mssql-tools/bin/sqlcmd -S $mssqlIP -U $mssqladminlogin -P ${mssqladminpass} -d ${moodledbname} -Q "exec sp_addrolemember 'db_owner','${moodledbuser}'" + + else + # Create postgres db + echo "${postgresIP}:5432:postgres:${pgadminlogin}:${pgadminpass}" > /root/.pgpass + chmod 600 /root/.pgpass + psql -h $postgresIP -U $pgadminlogin -c "CREATE DATABASE ${moodledbname};" postgres + psql -h $postgresIP -U $pgadminlogin -c "CREATE USER ${moodledbuser} WITH PASSWORD '${moodledbpass}';" postgres + psql -h $postgresIP -U $pgadminlogin -c "GRANT ALL ON DATABASE ${moodledbname} TO ${moodledbuser};" postgres + rm -f /root/.pgpass + fi + + # Master config for syslog + mkdir /var/log/sitelogs + chown syslog.adm /var/log/sitelogs + cat <> /etc/rsyslog.conf +\$ModLoad imudp +\$UDPServerRun 514 +EOF + cat <> /etc/rsyslog.d/40-sitelogs.conf +local1.* /var/log/sitelogs/moodle/access.log +local1.err /var/log/sitelogs/moodle/error.log +local2.* /var/log/sitelogs/moodle/cron.log +EOF + service rsyslog restart + + # Fire off moodle setup + if [ "$httpsTermination" = "None" ]; then + siteProtocol="http" + else + siteProtocol="https" + fi + if [ $dbServerType = "mysql" ]; then + echo -e "cd /tmp; /usr/bin/php /moodle/html/moodle/admin/cli/install.php --chmod=770 --lang=en_us --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 " + cd /tmp; /usr/bin/php /moodle/html/moodle/admin/cli/install.php --chmod=770 --lang=en_us --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 + + if [ "$installObjectFsSwitch" = "true" ]; then + mysql -h $mysqlIP -u $mysqladminlogin -p${mysqladminpass} ${moodledbname} -e "INSERT INTO mdl_config_plugins (plugin, name, value) VALUES ('tool_objectfs', 'enabletasks', 1);" + 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');" + mysql -h $mysqlIP -u $mysqladminlogin -p${mysqladminpass} ${moodledbname} -e "INSERT INTO mdl_config_plugins (plugin, name, value) VALUES ('tool_objectfs', 'azure_accountname', '${storageAccountName}');" + mysql -h $mysqlIP -u $mysqladminlogin -p${mysqladminpass} ${moodledbname} -e "INSERT INTO mdl_config_plugins (plugin, name, value) VALUES ('tool_objectfs', 'azure_container', 'objectfs');" + mysql -h $mysqlIP -u $mysqladminlogin -p${mysqladminpass} ${moodledbname} -e "INSERT INTO mdl_config_plugins (plugin, name, value) VALUES ('tool_objectfs', 'azure_sastoken', '${sas}');" + fi + elif [ $dbServerType = "mssql" ]; then + cd /tmp; /usr/bin/php /moodle/html/moodle/admin/cli/install.php --chmod=770 --lang=en_us --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 + + if [ "$installObjectFsSwitch" = "true" ]; then + /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)" + /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')" + /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}')" + /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')" + /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}')" + fi + else + echo -e "cd /tmp; /usr/bin/php /moodle/html/moodle/admin/cli/install.php --chmod=770 --lang=en_us --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 " + cd /tmp; /usr/bin/php /moodle/html/moodle/admin/cli/install.php --chmod=770 --lang=en_us --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 + + if [ "$installObjectFsSwitch" = "true" ]; then + # Add the ObjectFS configuration to Moodle. + echo "${postgresIP}:5432:${moodledbname}:${azuremoodledbuser}:${moodledbpass}" > /root/.pgpass + chmod 600 /root/.pgpass + psql -h $postgresIP -U $azuremoodledbuser -c "INSERT INTO mdl_config_plugins (plugin, name, value) VALUES ('tool_objectfs', 'enabletasks', 1);" $moodledbname + psql -h $postgresIP -U $azuremoodledbuser -c "INSERT INTO mdl_config_plugins (plugin, name, value) VALUES ('tool_objectfs', 'filesystem', '\tool_objectfs\azure_file_system');" $moodledbname + psql -h $postgresIP -U $azuremoodledbuser -c "INSERT INTO mdl_config_plugins (plugin, name, value) VALUES ('tool_objectfs', 'azure_accountname', '$storageAccountName');" $moodledbname + psql -h $postgresIP -U $azuremoodledbuser -c "INSERT INTO mdl_config_plugins (plugin, name, value) VALUES ('tool_objectfs', 'azure_container', 'objectfs');" $moodledbname + psql -h $postgresIP -U $azuremoodledbuser -c "INSERT INTO mdl_config_plugins (plugin, name, value) VALUES ('tool_objectfs', 'azure_sastoken', '$sas');" $moodledbname + fi + fi + + echo -e "\n\rDone! Installation completed!\n\r" + + # use /tmp/localcachedir/ for localcache and /var/www/html/moodle/ for core_component.php + dir="/var/www/html/moodle" + if [[ ! -d $dir ]]; then + mkdir -p $dir + chown -R www-data:www-data $dir + fi + sed -i "22 a \$CFG->localcachedir = '/tmp/localcachedir';" /moodle/html/moodle/config.php + sed -i "22 a \$CFG->alternative_component_cache = '/var/www/html/moodle/core_component.php';" /moodle/html/moodle/config.php + + if [ "$redisAuth" != "None" ]; then + create_redis_configuration_in_moodledata_muc_config_php + + # redis configuration in /moodle/html/moodle/config.php + sed -i "23 a \$CFG->session_redis_lock_expire = 7200;" /moodle/html/moodle/config.php + sed -i "23 a \$CFG->session_redis_acquire_lock_timeout = 120;" /moodle/html/moodle/config.php + sed -i "23 a \$CFG->session_redis_prefix = 'moodle_prod'; // Optional, default is don't set one." /moodle/html/moodle/config.php + sed -i "23 a \$CFG->session_redis_database = 0; // Optional, default is db 0." /moodle/html/moodle/config.php + sed -i "23 a \$CFG->session_redis_port = 6379; // Optional." /moodle/html/moodle/config.php + sed -i "23 a \$CFG->session_redis_host = '$redisDns';" /moodle/html/moodle/config.php + sed -i "23 a \$CFG->session_redis_auth = '$redisAuth';" /moodle/html/moodle/config.php + sed -i "23 a \$CFG->session_handler_class = '\\\core\\\session\\\redis';" /moodle/html/moodle/config.php + fi + + if [ "$httpsTermination" != "None" ]; then + # We proxy ssl, so moodle needs to know this + sed -i "23 a \$CFG->sslproxy = 'true';" /moodle/html/moodle/config.php + fi + + if [ "$searchType" = "elastic" ]; then + # Set up elasticsearch plugin + if [ "$tikaVmIP" = "none" ]; then + sed -i "23 a \$CFG->forced_plugin_settings = ['search_elastic' => ['hostname' => 'http://$elasticVm1IP']];" /moodle/html/moodle/config.php + else + 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 + fi + + sed -i "23 a \$CFG->searchengine = 'elastic';" /moodle/html/moodle/config.php + sed -i "23 a \$CFG->enableglobalsearch = 'true';" /moodle/html/moodle/config.php + # create index + php /moodle/html/moodle/search/cli/indexer.php --force --reindex + + elif [ "$searchType" = "azure" ]; then + # Set up Azure Search service plugin + if [ "$tikaVmIP" = "none" ]; then + sed -i "23 a \$CFG->forced_plugin_settings = ['search_azure' => ['searchurl' => 'https://$azureSearchNameHost', 'apikey' => '$azureSearchKey']];" /moodle/html/moodle/config.php + else + 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 + fi + + sed -i "23 a \$CFG->searchengine = 'azure';" /moodle/html/moodle/config.php + sed -i "23 a \$CFG->enableglobalsearch = 'true';" /moodle/html/moodle/config.php + # create index + php /moodle/html/moodle/search/cli/indexer.php --force --reindex + + fi + + if [ "$installObjectFsSwitch" = "true" ]; then + # Set the ObjectFS alternate filesystem + sed -i "23 a \$CFG->alternative_file_system_class = '\\\tool_objectfs\\\azure_file_system';" /moodle/html/moodle/config.php + fi + + if [ "$dbServerType" = "postgres" ]; then + # Get a new version of Postgres to match Azure version + add-apt-repository "deb http://apt.postgresql.org/pub/repos/apt/ xenial-pgdg main" + wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | sudo apt-key add - + apt-get update + apt-get install -y postgresql-client-9.6 + fi + + # create cron entry + # It is scheduled for once per minute. It can be changed as needed. + 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 + + # Set up cronned sql dump + if [ "$dbServerType" = "mysql" ]; then + cat < /etc/cron.d/sql-backup +22 02 * * * root /usr/bin/mysqldump -h $mysqlIP -u ${azuremoodledbuser} -p'${moodledbpass}' --databases ${moodledbname} | gzip > /moodle/db-backup.sql.gz +EOF + elif [ "$dbServerType" = "postgres" ]; then + cat < /etc/cron.d/sql-backup +22 02 * * * root /usr/bin/pg_dump -Fc -h $postgresIP -U ${azuremoodledbuser} ${moodledbname} > /moodle/db-backup.sql +EOF + #else # mssql. TODO It's missed earlier! Complete this! + fi + + # Turning off services we don't need the controller running + service nginx stop + service php${PhpVer}-fpm stop + service varnish stop + service varnishncsa stop + #service varnishlog stop + + # No need to run the commands below any more, as permissions & modes are already as such (no more "sudo -u www-data ...") + # Leaving this code as a remark that we are explicitly leaving the ownership to root:root +# if [ $fileServerType = "gluster" -o $fileServerType = "nfs" -o $fileServerType = "nfs-ha" ]; then +# # make sure Moodle can read its code directory but not write +# sudo chown -R root.root /moodle/html/moodle +# sudo find /moodle/html/moodle -type f -exec chmod 644 '{}' \; +# sudo find /moodle/html/moodle -type d -exec chmod 755 '{}' \; +# fi + # But now we need to adjust the moodledata and the certs directory ownerships, and the permission for the generated config.php + sudo chown -R www-data.www-data /moodle/moodledata /moodle/certs + sudo chmod +r /moodle/html/moodle/config.php + + # chmod /moodle for Azure NetApp Files (its default is 770!) + if [ $fileServerType = "nfs-byo" ]; then + sudo chmod +rx /moodle + fi + + if [ $fileServerType = "azurefiles" ]; then + # Delayed copy of moodle installation to the Azure Files share + + # First rename moodle directory to something else + mv /moodle /moodle_old_delete_me + # Then create the moodle share + echo -e '\n\rCreating an Azure Files share for moodle' + create_azure_files_moodle_share $storageAccountName $storageAccountKey /tmp/wabs.log $fileServerDiskSize + # Set up and mount Azure Files share. Must be done after nginx is installed because of www-data user/group + echo -e '\n\rSetting up and mounting Azure Files share on //'$storageAccountName'.file.core.windows.net/moodle on /moodle\n\r' + setup_and_mount_azure_files_moodle_share $storageAccountName $storageAccountKey + # Move the local installation over to the Azure Files + echo -e '\n\rMoving locally installed moodle over to Azure Files' + cp -a /moodle_old_delete_me/* /moodle || true # Ignore case sensitive directory copy failure + rm -rf /moodle_old_delete_me || true # Keep the files just in case + fi + + create_last_modified_time_update_script + run_once_last_modified_time_update_script + +} > /tmp/install.log diff --git a/scripts/install_tika.sh b/scripts/install_tika.sh new file mode 100644 index 00000000..82d3db58 --- /dev/null +++ b/scripts/install_tika.sh @@ -0,0 +1,105 @@ +#!/bin/bash +# Custom Script for Linux + +# The MIT License (MIT) +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +tikavmip=$1 + +echo $tikavmip >> /tmp/vars.txt + +{ + + # make sure the system does automatic update + sudo apt-get -y update + sudo apt-get -y install unattended-upgrades + + # download apache tika server + sudo wget -q http://mirrors.ocf.berkeley.edu/apache/tika/tika-server-1.18.jar --directory-prefix=/usr/share/java/ + + # install the required packages + sudo apt-get install -y openjdk-8-jre openjdk-8-jdk default-jre default-jdk + + # Configure tika + cat < /etc/systemd/system/tika-server.service +[Unit] +Description = Java Service +After network.target = tika-server.service + +[Service] +Type = forking +ExecStart = /usr/local/bin/tika-server start +ExecStop = /usr/local/bin/tika-server stop +ExecReload = /usr/local/bin/tika-server reload + +[Install] +WantedBy=multi-user.target +EOF + + chmod 777 /etc/systemd/system/tika-server.service + + cat < /usr/local/bin/tika-server +#!/bin/sh +SERVICE_NAME=tika-server +PATH_TO_JAR=/usr/share/java/tika-server-1.18.jar +PID_PATH_NAME=/var/run/tika-server-pid +case \$1 in + start) + echo "Starting \$SERVICE_NAME ..." + if [ ! -f \$PID_PATH_NAME ]; then + nohup java -jar \$PATH_TO_JAR --host=$tikavmip --port=9998 >> /var/log/tika-server.out 2>&1& + echo \$! > \$PID_PATH_NAME + echo "\$SERVICE_NAME started ..." + else + echo "\$SERVICE_NAME is already running ..." + fi + ;; + stop) + if [ -f \$PID_PATH_NAME ]; then + PID=\$(cat $PID_PATH_NAME); + echo "\$SERVICE_NAME stoping ..." + kill \$PID; + echo "\$SERVICE_NAME stopped ..." + rm \$PID_PATH_NAME + else + echo "\$SERVICE_NAME is not running ..." + fi + ;; + restart) + if [ -f \$PID_PATH_NAME ]; then + PID=\$(cat $PID_PATH_NAME); + echo "\$SERVICE_NAME stopping ..."; + kill \$PID; + echo "\$SERVICE_NAME stopped ..."; + rm \$PID_PATH_NAME + echo "\$SERVICE_NAME starting ..." + nohup java -jar \$PATH_TO_JAR --host=$tikavmip --port=9998 >> /var/log/tika-server.out 2>&1& + echo \$! > \$PID_PATH_NAME + echo "\$SERVICE_NAME started ..." + else + echo "\$SERVICE_NAME is not running ..." + fi + ;; +esac +EOF + chmod +x /usr/local/bin/tika-server + systemctl enable tika-server.service + systemctl start tika-server.service +} > /tmp/setup.log diff --git a/scripts/postgres_install_moodle.sh b/scripts/postgres_install_moodle.sh deleted file mode 100644 index 8630c30d..00000000 --- a/scripts/postgres_install_moodle.sh +++ /dev/null @@ -1,2131 +0,0 @@ -#!/bin/bash - -# The MIT License (MIT) -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in all -# copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -# SOFTWARE. - -#parameters -{ - moodleVersion=$1 - glusterNode=$2 - glusterVolume=$3 - siteFQDN=$4 - postgresIP=$5 - moodledbname=$6 - moodledbuser=$7 - moodledbpass=$8 - adminpass=$9 - pgadminlogin=$10 - pgadminpass=$11 - wabsacctname=$12 - wabsacctkey=$13 - azuremoodledbuser=$14 - redisDns=$15 - redisAuth=$16 - elasticVm1IP=$17 - - echo $moodleVersion >> /tmp/vars.txt - echo $glusterNode >> /tmp/vars.txt - echo $glusterVolume >> /tmp/vars.txt - echo $siteFQDN >> /tmp/vars.txt - echo $postgresIP >> /tmp/vars.txt - echo $moodledbname >> /tmp/vars.txt - echo $moodledbuser >> /tmp/vars.txt - echo $moodledbpass >> /tmp/vars.txt - echo $adminpass >> /tmp/vars.txt - echo $pgadminlogin >> /tmp/vars.txt - echo $pgadminpass >> /tmp/vars.txt - echo $wabsacctname >> /tmp/vars.txt - echo $wabsacctkey >> /tmp/vars.txt - echo $azuremoodledbuser >> /tmp/vars.txt - echo $redisDns >> /tmp/vars.txt - echo $redisAuth >> /tmp/vars.txt - echo $elasticVm1IP >> /tmp/vars.txt - - # make sure system does automatic updates and fail2ban - sudo apt-get -y update - sudo apt-get -y install unattended-upgrades fail2ban - - # configure fail2ban - cat < /etc/fail2ban/jail.conf -# Fail2Ban configuration file. -# -# This file was composed for Debian systems from the original one -# provided now under /usr/share/doc/fail2ban/examples/jail.conf -# for additional examples. -# -# Comments: use '#' for comment lines and ';' for inline comments -# -# To avoid merges during upgrades DO NOT MODIFY THIS FILE -# and rather provide your changes in /etc/fail2ban/jail.local -# - -# The DEFAULT allows a global definition of the options. They can be overridden -# in each jail afterwards. - -[DEFAULT] - -# "ignoreip" can be an IP address, a CIDR mask or a DNS host. Fail2ban will not -# ban a host which matches an address in this list. Several addresses can be -# defined using space separator. -ignoreip = 127.0.0.1/8 - -# "bantime" is the number of seconds that a host is banned. -bantime = 600 - -# A host is banned if it has generated "maxretry" during the last "findtime" -# seconds. -findtime = 600 -maxretry = 3 - -# "backend" specifies the backend used to get files modification. -# Available options are "pyinotify", "gamin", "polling" and "auto". -# This option can be overridden in each jail as well. -# -# pyinotify: requires pyinotify (a file alteration monitor) to be installed. -# If pyinotify is not installed, Fail2ban will use auto. -# gamin: requires Gamin (a file alteration monitor) to be installed. -# If Gamin is not installed, Fail2ban will use auto. -# polling: uses a polling algorithm which does not require external libraries. -# auto: will try to use the following backends, in order: -# pyinotify, gamin, polling. -backend = auto - -# "usedns" specifies if jails should trust hostnames in logs, -# warn when reverse DNS lookups are performed, or ignore all hostnames in logs -# -# yes: if a hostname is encountered, a reverse DNS lookup will be performed. -# warn: if a hostname is encountered, a reverse DNS lookup will be performed, -# but it will be logged as a warning. -# no: if a hostname is encountered, will not be used for banning, -# but it will be logged as info. -usedns = warn - -# -# Destination email address used solely for the interpolations in -# jail.{conf,local} configuration files. -destemail = root@localhost - -# -# Name of the sender for mta actions -sendername = Fail2Ban - -# -# ACTIONS -# - -# Default banning action (e.g. iptables, iptables-new, -# iptables-multiport, shorewall, etc) It is used to define -# action_* variables. Can be overridden globally or per -# section within jail.local file -banaction = iptables-multiport - -# email action. Since 0.8.1 upstream fail2ban uses sendmail -# MTA for the mailing. Change mta configuration parameter to mail -# if you want to revert to conventional 'mail'. -mta = sendmail - -# Default protocol -protocol = tcp - -# Specify chain where jumps would need to be added in iptables-* actions -chain = INPUT - -# -# Action shortcuts. To be used to define action parameter - -# The simplest action to take: ban only -action_ = %(banaction)s[name=%(__name__)s, port="%(port)s", protocol="%(protocol)s", chain="%(chain)s"] - -# ban & send an e-mail with whois report to the destemail. -action_mw = %(banaction)s[name=%(__name__)s, port="%(port)s", protocol="%(protocol)s", chain="%(chain)s"] - %(mta)s-whois[name=%(__name__)s, dest="%(destemail)s", protocol="%(protocol)s", chain="%(chain)s", sendername="%(sendername)s"] - -# ban & send an e-mail with whois report and relevant log lines -# to the destemail. -action_mwl = %(banaction)s[name=%(__name__)s, port="%(port)s", protocol="%(protocol)s", chain="%(chain)s"] - %(mta)s-whois-lines[name=%(__name__)s, dest="%(destemail)s", logpath=%(logpath)s, chain="%(chain)s", sendername="%(sendername)s"] - -# Choose default action. To change, just override value of 'action' with the -# interpolation to the chosen action shortcut (e.g. action_mw, action_mwl, etc) in jail.local -# globally (section [DEFAULT]) or per specific section -action = %(action_)s - -# -# JAILS -# - -# Next jails corresponds to the standard configuration in Fail2ban 0.6 which -# was shipped in Debian. Enable any defined here jail by including -# -# [SECTION_NAME] -# enabled = true - -# -# in /etc/fail2ban/jail.local. -# -# Optionally you may override any other parameter (e.g. banaction, -# action, port, logpath, etc) in that section within jail.local - -[ssh] - -enabled = true -port = ssh -filter = sshd -logpath = /var/log/auth.log -maxretry = 6 - -[dropbear] - -enabled = false -port = ssh -filter = dropbear -logpath = /var/log/auth.log -maxretry = 6 - -# Generic filter for pam. Has to be used with action which bans all ports -# such as iptables-allports, shorewall -[pam-generic] - -enabled = false -# pam-generic filter can be customized to monitor specific subset of 'tty's -filter = pam-generic -# port actually must be irrelevant but lets leave it all for some possible uses -port = all -banaction = iptables-allports -port = anyport -logpath = /var/log/auth.log -maxretry = 6 - -[xinetd-fail] - -enabled = false -filter = xinetd-fail -port = all -banaction = iptables-multiport-log -logpath = /var/log/daemon.log -maxretry = 2 - - -[ssh-ddos] - -enabled = false -port = ssh -filter = sshd-ddos -logpath = /var/log/auth.log -maxretry = 6 - - -# Here we use blackhole routes for not requiring any additional kernel support -# to store large volumes of banned IPs - -[ssh-route] - -enabled = false -filter = sshd -action = route -logpath = /var/log/sshd.log -maxretry = 6 - -# Here we use a combination of Netfilter/Iptables and IPsets -# for storing large volumes of banned IPs -# -# IPset comes in two versions. See ipset -V for which one to use -# requires the ipset package and kernel support. -[ssh-iptables-ipset4] - -enabled = false -port = ssh -filter = sshd -banaction = iptables-ipset-proto4 -logpath = /var/log/sshd.log -maxretry = 6 - -[ssh-iptables-ipset6] - -enabled = false -port = ssh -filter = sshd -banaction = iptables-ipset-proto6 -logpath = /var/log/sshd.log -maxretry = 6 - - -# -# HTTP servers -# - -[apache] - -enabled = false -port = http,https -filter = apache-auth -logpath = /var/log/apache*/*error.log -maxretry = 6 - -# default action is now multiport, so apache-multiport jail was left -# for compatibility with previous (<0.7.6-2) releases -[apache-multiport] - -enabled = false -port = http,https -filter = apache-auth -logpath = /var/log/apache*/*error.log -maxretry = 6 - -[apache-noscript] - -enabled = false -port = http,https -filter = apache-noscript -logpath = /var/log/apache*/*error.log -maxretry = 6 - -[apache-overflows] - -enabled = false -port = http,https -filter = apache-overflows -logpath = /var/log/apache*/*error.log -maxretry = 2 - -# Ban attackers that try to use PHP's URL-fopen() functionality -# through GET/POST variables. - Experimental, with more than a year -# of usage in production environments. - -[php-url-fopen] - -enabled = false -port = http,https -filter = php-url-fopen -logpath = /var/www/*/logs/access_log - -# A simple PHP-fastcgi jail which works with lighttpd. -# If you run a lighttpd server, then you probably will -# find these kinds of messages in your error_log: -# ALERT – tried to register forbidden variable ‘GLOBALS’ -# through GET variables (attacker '1.2.3.4', file '/var/www/default/htdocs/index.php') - -[lighttpd-fastcgi] - -enabled = false -port = http,https -filter = lighttpd-fastcgi -logpath = /var/log/lighttpd/error.log - -# Same as above for mod_auth -# It catches wrong authentifications - -[lighttpd-auth] - -enabled = false -port = http,https -filter = suhosin -logpath = /var/log/lighttpd/error.log - -[nginx-http-auth] - -enabled = false -filter = nginx-http-auth -port = http,https -logpath = /var/log/nginx/error.log - -# Monitor roundcube server - -[roundcube-auth] - -enabled = false -filter = roundcube-auth -port = http,https -logpath = /var/log/roundcube/userlogins - - -[sogo-auth] - -enabled = false -filter = sogo-auth -port = http, https -# without proxy this would be: -# port = 20000 -logpath = /var/log/sogo/sogo.log - - -# -# FTP servers -# - -[vsftpd] - -enabled = false -port = ftp,ftp-data,ftps,ftps-data -filter = vsftpd -logpath = /var/log/vsftpd.log -# or overwrite it in jails.local to be -# logpath = /var/log/auth.log -# if you want to rely on PAM failed login attempts -# vsftpd's failregex should match both of those formats -maxretry = 6 - - -[proftpd] - -enabled = false -port = ftp,ftp-data,ftps,ftps-data -filter = proftpd -logpath = /var/log/proftpd/proftpd.log -maxretry = 6 - - -[pure-ftpd] - -enabled = false -port = ftp,ftp-data,ftps,ftps-data -filter = pure-ftpd -logpath = /var/log/syslog -maxretry = 6 - - -[wuftpd] - -enabled = false -port = ftp,ftp-data,ftps,ftps-data -filter = wuftpd -logpath = /var/log/syslog -maxretry = 6 - - -# -# Mail servers -# - -[postfix] - -enabled = false -port = smtp,ssmtp,submission -filter = postfix -logpath = /var/log/mail.log - - -[couriersmtp] - -enabled = false -port = smtp,ssmtp,submission -filter = couriersmtp -logpath = /var/log/mail.log - - -# -# Mail servers authenticators: might be used for smtp,ftp,imap servers, so -# all relevant ports get banned -# - -[courierauth] - -enabled = false -port = smtp,ssmtp,submission,imap2,imap3,imaps,pop3,pop3s -filter = courierlogin -logpath = /var/log/mail.log - - -[sasl] - -enabled = false -port = smtp,ssmtp,submission,imap2,imap3,imaps,pop3,pop3s -filter = postfix-sasl -# You might consider monitoring /var/log/mail.warn instead if you are -# running postfix since it would provide the same log lines at the -# "warn" level but overall at the smaller filesize. -logpath = /var/log/mail.log - -[dovecot] - -enabled = false -port = smtp,ssmtp,submission,imap2,imap3,imaps,pop3,pop3s -filter = dovecot -logpath = /var/log/mail.log - -# To log wrong MySQL access attempts add to /etc/my.cnf: -# log-error=/var/log/mysqld.log -# log-warning = 2 -[mysqld-auth] - -enabled = false -filter = mysqld-auth -port = 3306 -logpath = /var/log/mysqld.log - - -# DNS Servers - - -# These jails block attacks against named (bind9). By default, logging is off -# with bind9 installation. You will need something like this: -# -# logging { -# channel security_file { -# file "/var/log/named/security.log" versions 3 size 30m; -# severity dynamic; -# print-time yes; -# }; -# category security { -# security_file; -# }; -# }; -# -# in your named.conf to provide proper logging - -# !!! WARNING !!! -# Since UDP is connection-less protocol, spoofing of IP and imitation -# of illegal actions is way too simple. Thus enabling of this filter -# might provide an easy way for implementing a DoS against a chosen -# victim. See -# http://nion.modprobe.de/blog/archives/690-fail2ban-+-dns-fail.html -# Please DO NOT USE this jail unless you know what you are doing. -#[named-refused-udp] -# -#enabled = false -#port = domain,953 -#protocol = udp -#filter = named-refused -#logpath = /var/log/named/security.log - -[named-refused-tcp] - -enabled = false -port = domain,953 -protocol = tcp -filter = named-refused -logpath = /var/log/named/security.log - -# Multiple jails, 1 per protocol, are necessary ATM: -# see https://github.com/fail2ban/fail2ban/issues/37 -[asterisk-tcp] - -enabled = false -filter = asterisk -port = 5060,5061 -protocol = tcp -logpath = /var/log/asterisk/messages - -[asterisk-udp] - -enabled = false -filter = asterisk -port = 5060,5061 -protocol = udp -logpath = /var/log/asterisk/messages - - -# Jail for more extended banning of persistent abusers -# !!! WARNING !!! -# Make sure that your loglevel specified in fail2ban.conf/.local -# is not at DEBUG level -- which might then cause fail2ban to fall into -# an infinite loop constantly feeding itself with non-informative lines -[recidive] - -enabled = false -filter = recidive -logpath = /var/log/fail2ban.log -action = iptables-allports[name=recidive] - sendmail-whois-lines[name=recidive, logpath=/var/log/fail2ban.log] -bantime = 604800 ; 1 week -findtime = 86400 ; 1 day -maxretry = 5 -EOF - - # create gluster mount point - mkdir -p /moodle - - export DEBIAN_FRONTEND=noninteractive - - # configure gluster repository & install gluster client - sudo add-apt-repository ppa:gluster/glusterfs-3.8 -y >> /tmp/apt1.log - sudo apt-get -y update >> /tmp/apt2.log - sudo apt-get -y --force-yes install rsyslog glusterfs-client postgresql-client git >> /tmp/apt3.log - - # install azure cli & setup container - echo "deb [arch=amd64] https://packages.microsoft.com/repos/azure-cli/ wheezy main" | \ - sudo tee /etc/apt/sources.list.d/azure-cli.list - - sudo apt-key adv --keyserver packages.microsoft.com --recv-keys 52E16F86FEE04B979B07E28DB02C46DF417A0893 >> /tmp/apt4.log - sudo apt-get -y install apt-transport-https >> /tmp/apt4.log - sudo apt-get -y update > /dev/null - sudo apt-get -y install azure-cli >> /tmp/apt4.log - - az storage container create \ - --name objectfs \ - --account-name $wabsacctname \ - --account-key $wabsacctkey \ - --public-access off \ - --fail-on-exist >> /tmp/wabs.log - - az storage container policy create \ - --account-name $wabsacctname \ - --account-key $wabsacctkey \ - --container-name objectfs \ - --name readwrite \ - --start $(date --date="1 day ago" +%F) \ - --expiry $(date --date="2199-01-01" +%F) \ - --permissions rw >> /tmp/wabs.log - - sas=$(az storage container generate-sas \ - --account-name $wabsacctname \ - --account-key $wabsacctkey \ - --name objectfs \ - --policy readwrite \ - --output tsv) - - # mount gluster files system - echo -e '\n\rInstalling GlusterFS on '$glusterNode':/'$glusterVolume '/moodle\n\r' - sudo mount -t glusterfs $glusterNode:/$glusterVolume /moodle - - - - # install pre-requisites - sudo apt-get install -y --fix-missing python-software-properties unzip - - # install the entire stack - sudo apt-get -y --force-yes install nginx php-fpm varnish >> /tmp/apt5a.log - sudo apt-get -y --force-yes install php php-cli php-curl php-zip >> /tmp/apt5b.log - - # Moodle requirements - sudo apt-get -y update > /dev/null - sudo apt-get install -y --force-yes graphviz aspell php-common php-soap php-json php-redis > /tmp/apt6.log - sudo apt-get install -y --force-yes php-bcmath php-gd php-pgsql php-xmlrpc php-intl php-xml php-bz2 >> /tmp/apt6.log - - - # Set up initial moodle dirs - mkdir -p /moodle/html - mkdir -p /moodle/certs - mkdir -p /moodle/moodledata - chown -R www-data.www-data /moodle - - # install Moodle - echo '#!/bin/bash - cd /tmp - - # downloading moodle - /usr/bin/curl -k --max-redirs 10 https://github.com/moodle/moodle/archive/'$moodleVersion'.zip -L -o moodle.zip - /usr/bin/unzip -q moodle.zip - /bin/mv -v moodle-'$moodleVersion' /moodle/html/moodle - - # Commented out as the plugin is not available - # install Office 365 plugins - #if [ "$installOfficePlugins" = "True" ]; then - # curl -k --max-redirs 10 https://github.com/Microsoft/o365-moodle/archive/'$moodleVersion'.zip -L -o o365.zip - # unzip -q o365.zip - # cp -r o365-moodle-'$moodleVersion'/* /moodle/html/moodle - # rm -rf o365-moodle-'$moodleVersion' - #fi - - # Install ElasticSearch plugin - /usr/bin/curl -k --max-redirs 10 https://github.com/catalyst/moodle-search_elastic/archive/master.zip -L -o plugin-elastic.zip - /usr/bin/unzip -q plugin-elastic.zip - /bin/mkdir -p /moodle/html/moodle/search/engine/elastic - /bin/cp -r moodle-search_elastic-master/* /moodle/html/moodle/search/engine/elastic - /bin/rm -rf moodle-search_elastic-master - - # Install ElasticSearch plugin dependency - /usr/bin/curl -k --max-redirs 10 https://github.com/catalyst/moodle-local_aws/archive/master.zip -L -o local-aws.zip - /usr/bin/unzip -q local-aws.zip - /bin/mkdir -p /moodle/html/moodle/local/aws - /bin/cp -r moodle-local_aws-master/* /moodle/html/moodle/local/aws - - # Install the ObjectFS plugin - /usr/bin/curl -k --max-redirs 10 https://github.com/catalyst/moodle-tool_objectfs/archive/master.zip -L -o plugin-objectfs.zip - /usr/bin/unzip -q plugin-objectfs.zip - /bin/mkdir -p /moodle/html/moodle/admin/tool/objectfs - /bin/cp -r moodle-tool_objectfs-master/* /moodle/html/moodle/admin/tool/objectfs - /bin/rm -rf moodle-tool_objectfs-master - - # Install the ObjectFS Azure library - /usr/bin/curl -k --max-redirs 10 https://github.com/catalyst/moodle-local_azure_storage/archive/master.zip -L -o plugin-azurelibrary.zip - /usr/bin/unzip -q plugin-azurelibrary.zip - /bin/mkdir -p /moodle/html/moodle/local/azure_storage - /bin/cp -r moodle-local_azure_storage-master/* /moodle/html/moodle/local/azure_storage - /bin/rm -rf moodle-local_azure_storage-master - ' > /tmp/setup-moodle.sh - - chmod 755 /tmp/setup-moodle.sh - sudo -u www-data /tmp/setup-moodle.sh >> /tmp/setupmoodle.log - - # create cron entry - # It is scheduled for once per day. It can be changed as needed. - 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 - - # Build nginx config - cat < /etc/nginx/nginx.conf -user www-data; -worker_processes 2; -pid /run/nginx.pid; - -events { - worker_connections 768; -} - -http { - - sendfile on; - tcp_nopush on; - tcp_nodelay on; - keepalive_timeout 65; - types_hash_max_size 2048; - client_max_body_size 0; - proxy_max_temp_file_size 0; - server_names_hash_bucket_size 128; - fastcgi_buffers 16 16k; - fastcgi_buffer_size 32k; - proxy_buffering off; - include /etc/nginx/mime.types; - default_type application/octet-stream; - - access_log /var/log/nginx/access.log; - error_log /var/log/nginx/error.log; - - set_real_ip_from 127.0.0.1; - real_ip_header X-Forwarded-For; - ssl_protocols TLSv1 TLSv1.1 TLSv1.2; # Dropping SSLv3, ref: POODLE - ssl_prefer_server_ciphers on; - - gzip on; - gzip_disable "msie6"; - gzip_vary on; - gzip_proxied any; - gzip_comp_level 6; - gzip_buffers 16 8k; - gzip_http_version 1.1; - gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript; - - map \$http_x_forwarded_proto \$fastcgi_https { - default \$https; - http ''; - https on; - } - - log_format moodle_combined '\$remote_addr - \$upstream_http_x_moodleuser [\$time_local] ' - '"\$request" \$status \$body_bytes_sent ' - '"\$http_referer" "\$http_user_agent"'; - - - include /etc/nginx/conf.d/*.conf; - include /etc/nginx/sites-enabled/*; -} -EOF - - cat <> /etc/nginx/sites-enabled/${siteFQDN}.conf -server { - listen 81 default; - server_name ${siteFQDN}; - root /moodle/html/moodle; - index index.php index.html index.htm; - - # Log to syslog - error_log syslog:server=localhost,facility=local1,severity=error,tag=moodle; - access_log syslog:server=localhost,facility=local1,severity=notice,tag=moodle moodle_combined; - - # Log XFF IP instead of varnish - set_real_ip_from 10.0.0.0/8; - set_real_ip_from 127.0.0.1; - set_real_ip_from 172.16.0.0/12; - set_real_ip_from 192.168.0.0/16; - real_ip_header X-Forwarded-For; - real_ip_recursive on; - - - # Redirect to https - if (\$http_x_forwarded_proto != https) { - return 301 https://\$server_name\$request_uri; - } - rewrite ^/(.*\.php)(/)(.*)$ /\$1?file=/\$3 last; - - - # Filter out php-fpm status page - location ~ ^/server-status { - return 404; - } - - location / { - try_files \$uri \$uri/index.php?\$query_string; - } - - location ~ [^/]\.php(/|$) { - fastcgi_split_path_info ^(.+?\.php)(/.*)$; - if (!-f \$document_root\$fastcgi_script_name) { - return 404; - } - - fastcgi_buffers 16 16k; - fastcgi_buffer_size 32k; - fastcgi_param SCRIPT_FILENAME \$document_root\$fastcgi_script_name; - fastcgi_pass unix:/run/php/php7.0-fpm.sock; - fastcgi_read_timeout 3600; - fastcgi_index index.php; - include fastcgi_params; - } -} - -server { - listen 443 ssl; - root /moodle/html/moodle; - index index.php index.html index.htm; - - ssl on; - ssl_certificate /moodle/certs/nginx.crt; - ssl_certificate_key /moodle/certs/nginx.key; - - # Log to syslog - error_log syslog:server=localhost,facility=local1,severity=error,tag=moodle; - access_log syslog:server=localhost,facility=local1,severity=notice,tag=moodle moodle_combined; - - # Log XFF IP instead of varnish - set_real_ip_from 10.0.0.0/8; - set_real_ip_from 127.0.0.1; - set_real_ip_from 172.16.0.0/12; - set_real_ip_from 192.168.0.0/16; - real_ip_header X-Forwarded-For; - real_ip_recursive on; - - location / { - proxy_set_header Host \$host; - proxy_set_header HTTP_REFERER \$http_referer; - proxy_set_header X-Forwarded-Host \$host; - proxy_set_header X-Forwarded-Server \$host; - proxy_set_header X-Forwarded-Proto https; - proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for; - proxy_pass http://localhost:80; - } -} -EOF - - echo -e "Generating SSL self-signed certificate" - openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout /moodle/certs/nginx.key -out /moodle/certs/nginx.crt -subj "/C=BR/ST=SP/L=SaoPaulo/O=IT/CN=$siteFQDN" - - # php config - PhpIni=/etc/php/7.0/fpm/php.ini - sed -i "s/memory_limit.*/memory_limit = 512M/" $PhpIni - sed -i "s/max_execution_time.*/max_execution_time = 18000/" $PhpIni - sed -i "s/max_input_vars.*/max_input_vars = 100000/" $PhpIni - sed -i "s/max_input_time.*/max_input_time = 600/" $PhpIni - sed -i "s/upload_max_filesize.*/upload_max_filesize = 1024M/" $PhpIni - sed -i "s/post_max_size.*/post_max_size = 1056M/" $PhpIni - sed -i "s/;opcache.use_cwd.*/opcache.use_cwd = 1/" $PhpIni - sed -i "s/;opcache.validate_timestamps.*/opcache.validate_timestamps = 1/" $PhpIni - sed -i "s/;opcache.save_comments.*/opcache.save_comments = 1/" $PhpIni - sed -i "s/;opcache.enable_file_override.*/opcache.enable_file_override = 0/" $PhpIni - sed -i "s/;opcache.enable.*/opcache.enable = 1/" $PhpIni - sed -i "s/;opcache.memory_consumption.*/opcache.memory_consumption = 256/" $PhpIni - sed -i "s/;opcache.max_accelerated_files.*/opcache.max_accelerated_files = 8000/" $PhpIni - - # fpm config - overload this - cat < /etc/php/7.0/fpm/pool.d/www.conf -[www] -user = www-data -group = www-data -listen = /run/php/php7.0-fpm.sock -listen.owner = www-data -listen.group = www-data -pm = dynamic -pm.max_children = 3000 -pm.start_servers = 20 -pm.min_spare_servers = 22 -pm.max_spare_servers = 30 -EOF - - - # Remove the default site. Moodle is the only site we want - rm -f /etc/nginx/sites-enabled/default - - # restart Nginx - sudo service nginx restart - - # Configure varnish startup for 16.04 - 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" - sed -i "s/^ExecStart.*/${VARNISHSTART}/" /lib/systemd/system/varnish.service - - # Configure varnish VCL for moodle - cat <> /etc/varnish/moodle.vcl -vcl 4.0; - -import std; -import directors; -backend default { - .host = "localhost"; - .port = "81"; - .first_byte_timeout = 3600s; - .connect_timeout = 600s; - .between_bytes_timeout = 600s; -} - -sub vcl_recv { - # Varnish does not support SPDY or HTTP/2.0 untill we upgrade to Varnish 5.0 - if (req.method == "PRI") { - return (synth(405)); - } - - if (req.restarts == 0) { - if (req.http.X-Forwarded-For) { - set req.http.X-Forwarded-For = req.http.X-Forwarded-For + ", " + client.ip; - } else { - set req.http.X-Forwarded-For = client.ip; - } - } - - # Non-RFC2616 or CONNECT HTTP requests methods filtered. Pipe requests directly to backend - if (req.method != "GET" && - req.method != "HEAD" && - req.method != "PUT" && - req.method != "POST" && - req.method != "TRACE" && - req.method != "OPTIONS" && - req.method != "DELETE") { - return (pipe); - } - - # Varnish don't mess with healthchecks - if (req.url ~ "^/admin/tool/heartbeat" || req.url ~ "^/healthcheck.php") - { - return (pass); - } - - # Pipe requests to backup.php straight to backend - prevents problem with progress bar long polling 503 problem - # This is here because backup.php is POSTing to itself - Filter before !GET&&!HEAD - if (req.url ~ "^/backup/backup.php") - { - return (pipe); - } - - # Varnish only deals with GET and HEAD by default. If request method is not GET or HEAD, pass request to backend - if (req.method != "GET" && req.method != "HEAD") { - return (pass); - } - - ### Rules for Moodle and Totara sites ### - # Moodle doesn't require Cookie to serve following assets. Remove Cookie header from request, so it will be looked up. - if ( req.url ~ "^/altlogin/.+/.+\.(png|jpg|jpeg|gif|css|js|webp)$" || - req.url ~ "^/pix/.+\.(png|jpg|jpeg|gif)$" || - req.url ~ "^/theme/font.php" || - req.url ~ "^/theme/image.php" || - req.url ~ "^/theme/javascript.php" || - req.url ~ "^/theme/jquery.php" || - req.url ~ "^/theme/styles.php" || - req.url ~ "^/theme/yui" || - req.url ~ "^/lib/javascript.php/-1/" || - req.url ~ "^/lib/requirejs.php/-1/" - ) - { - set req.http.X-Long-TTL = "86400"; - unset req.http.Cookie; - return(hash); - } - - # Perform lookup for selected assets that we know are static but Moodle still needs a Cookie - if( req.url ~ "^/theme/.+\.(png|jpg|jpeg|gif|css|js|webp)" || - req.url ~ "^/lib/.+\.(png|jpg|jpeg|gif|css|js|webp)" || - req.url ~ "^/pluginfile.php/[0-9]+/course/overviewfiles/.+\.(?i)(png|jpg)$" - ) - { - # Set internal temporary header, based on which we will do things in vcl_backend_response - set req.http.X-Long-TTL = "86400"; - return (hash); - } - - # Serve requests to SCORM checknet.txt from varnish. Have to remove get parameters. Response body always contains "1" - if ( req.url ~ "^/lib/yui/build/moodle-core-checknet/assets/checknet.txt" ) - { - set req.url = regsub(req.url, "(.*)\?.*", "\1"); - unset req.http.Cookie; # Will go to hash anyway at the end of vcl_recv - set req.http.X-Long-TTL = "86400"; - return(hash); - } - - # Requests containing "Cookie" or "Authorization" headers will not be cached - if (req.http.Authorization || req.http.Cookie) { - return (pass); - } - - # Almost everything in Moodle correctly serves Cache-Control headers, if - # needed, which varnish will honor, but there are some which don't. Rather - # than explicitly finding them all and listing them here we just fail safe - # and don't cache unknown urls that get this far. - return (pass); -} - -sub vcl_backend_response { - # Happens after we have read the response headers from the backend. - # - # Here you clean the response headers, removing silly Set-Cookie headers - # and other mistakes your backend does. - - # We know these assest are static, let's set TTL >0 and allow client caching - 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 ) - { # If max-age < defined in X-Long-TTL header - set beresp.http.X-Orig-Pragma = beresp.http.Pragma; unset beresp.http.Pragma; - set beresp.http.X-Orig-Cache-Control = beresp.http.Cache-Control; - set beresp.http.Cache-Control = "public, max-age="+bereq.http.X-Long-TTL+", no-transform"; - set beresp.ttl = std.duration(bereq.http.X-Long-TTL + "s", 1s); - unset bereq.http.X-Long-TTL; - } - else if( !beresp.http.Cache-Control && bereq.http.X-Long-TTL && !beresp.http.WWW-Authenticate ) { - set beresp.http.X-Orig-Pragma = beresp.http.Pragma; unset beresp.http.Pragma; - set beresp.http.Cache-Control = "public, max-age="+bereq.http.X-Long-TTL+", no-transform"; - set beresp.ttl = std.duration(bereq.http.X-Long-TTL + "s", 1s); - unset bereq.http.X-Long-TTL; - } - else { # Don't touch headers if max-age > defined in X-Long-TTL header - unset bereq.http.X-Long-TTL; - } - - # Here we set X-Trace header, prepending it to X-Trace header received from backend. Useful for troubleshooting - if(beresp.http.x-trace && !beresp.was_304) { - 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; - } - else { - set beresp.http.X-Trace = regsub(server.identity, "^([^.]+),?.*$", "\1")+"->"+regsub(beresp.backend.name, "^(.+)\((?:[0-9]{1,3}\.){3}([0-9]{1,3})\)","\1(\2)"); - } - - # Gzip JS, CSS is done at the ngnix level doing it here dosen't respect the no buffer requsets - # if (beresp.http.content-type ~ "application/javascript.*" || beresp.http.content-type ~ "text") { - # set beresp.do_gzip = true; - #} -} - -sub vcl_deliver { - - # Revert back to original Cache-Control header before delivery to client - if (resp.http.X-Orig-Cache-Control) - { - set resp.http.Cache-Control = resp.http.X-Orig-Cache-Control; - unset resp.http.X-Orig-Cache-Control; - } - - # Revert back to original Pragma header before delivery to client - if (resp.http.X-Orig-Pragma) - { - set resp.http.Pragma = resp.http.X-Orig-Pragma; - unset resp.http.X-Orig-Pragma; - } - - # (Optional) X-Cache HTTP header will be added to responce, indicating whether object was retrieved from backend, or served from cache - if (obj.hits > 0) { - set resp.http.X-Cache = "HIT"; - } else { - set resp.http.X-Cache = "MISS"; - } - - # Set X-AuthOK header when totara/varnsih authentication succeeded - if (req.http.X-AuthOK) { - set resp.http.X-AuthOK = req.http.X-AuthOK; - } - - # If desired "Via: 1.1 Varnish-v4" response header can be removed from response - unset resp.http.Via; - unset resp.http.Server; - - return(deliver); -} - -sub vcl_backend_error { - # More comprehensive varnish error page. Display time, instance hostname, host header, url for easier troubleshooting. - set beresp.http.Content-Type = "text/html; charset=utf-8"; - set beresp.http.Retry-After = "5"; - synthetic( {" - - - - "} + beresp.status + " " + beresp.reason + {" - - -

Error "} + beresp.status + " " + beresp.reason + {"

-

"} + beresp.reason + {"

-

Guru Meditation:

-

Time: "} + now + {"

-

Node: "} + server.hostname + {"

-

Host: "} + bereq.http.host + {"

-

URL: "} + bereq.url + {"

-

XID: "} + bereq.xid + {"

-
-

Varnish cache server - - - "} ); - return (deliver); -} - -sub vcl_synth { - - #Redirect using '301 - Permanent Redirect', permanent redirect - if (resp.status == 851) { - set resp.http.Location = req.http.x-redir; - set resp.http.X-Varnish-Redirect = true; - set resp.status = 301; - return (deliver); - } - - #Redirect using '302 - Found', temporary redirect - if (resp.status == 852) { - set resp.http.Location = req.http.x-redir; - set resp.http.X-Varnish-Redirect = true; - set resp.status = 302; - return (deliver); - } - - #Redirect using '307 - Temporary Redirect', !GET&&!HEAD requests, dont change method on redirected requests - if (resp.status == 857) { - set resp.http.Location = req.http.x-redir; - set resp.http.X-Varnish-Redirect = true; - set resp.status = 307; - return (deliver); - } - - #Respond with 403 - Forbidden - if (resp.status == 863) { - set resp.http.X-Varnish-Error = true; - set resp.status = 403; - return (deliver); - } -} -EOF - - # Restart Varnish - systemctl daemon-reload - service varnish restart - - # Create postgres db - echo "${postgresIP}:5432:postgres:${pgadminlogin}:${pgadminpass}" > /root/.pgpass - chmod 600 /root/.pgpass - psql -h $postgresIP -U $pgadminlogin -c "CREATE DATABASE ${moodledbname};" postgres - psql -h $postgresIP -U $pgadminlogin -c "CREATE USER ${moodledbuser} WITH PASSWORD '${moodledbpass}';" postgres - psql -h $postgresIP -U $pgadminlogin -c "GRANT ALL ON DATABASE ${moodledbname} TO ${moodledbuser};" postgres - rm -f /root/.pgpass - - # Master config for syslog - mkdir /var/log/sitelogs - chown syslog.adm /var/log/sitelogs - cat <> /etc/rsyslog.conf -\$ModLoad imudp -\$UDPServerRun 514 -EOF - cat <> /etc/rsyslog.d/40-sitelogs.conf -local1.* /var/log/sitelogs/moodle/access.log -local1.err /var/log/sitelogs/moodle/error.log -local2.* /var/log/sitelogs/moodle/cron.log -EOF - service rsyslog restart - - # Fire off moodle setup - echo -e "cd /tmp; sudo -u www-data /usr/bin/php /moodle/html/moodle/admin/cli/install.php --chmod=770 --lang=en_us --wwwroot=https://"$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 " - cd /tmp; sudo -u www-data /usr/bin/php /moodle/html/moodle/admin/cli/install.php --chmod=770 --lang=en_us --wwwroot=https://$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 - - # Add the ObjectFS configuration to Moodle. - echo "${postgresIP}:5432:${moodledbname}:${azuremoodledbuser}:${moodledbpass}" > /root/.pgpass - chmod 600 /root/.pgpass - psql -h $postgresIP -U $azuremoodledbuser -c "INSERT INTO mdl_config_plugins (plugin, name, value) VALUES ('tool_objectfs', 'enabletasks', 1);" $moodledbname - psql -h $postgresIP -U $azuremoodledbuser -c "INSERT INTO mdl_config_plugins (plugin, name, value) VALUES ('tool_objectfs', 'filesystem', '\tool_objectfs\azure_file_system');" $moodledbname - psql -h $postgresIP -U $azuremoodledbuser -c "INSERT INTO mdl_config_plugins (plugin, name, value) VALUES ('tool_objectfs', 'azure_accountname', '$wabsacctname');" $moodledbname - psql -h $postgresIP -U $azuremoodledbuser -c "INSERT INTO mdl_config_plugins (plugin, name, value) VALUES ('tool_objectfs', 'azure_container', 'objectfs');" $moodledbname - psql -h $postgresIP -U $azuremoodledbuser -c "INSERT INTO mdl_config_plugins (plugin, name, value) VALUES ('tool_objectfs', 'azure_sastoken', '$sas');" $moodledbname - - echo -e "\n\rDone! Installation completed!\n\r" - - # create redis configuration in /moodle/moodledata/muc/config.php - cat < /moodle/moodledata/muc/config.php - '7a142be09ea65699e4a6f6ef91c0773c', - 'stores' => - array ( - 'default_application' => - array ( - 'name' => 'default_application', - 'plugin' => 'file', - 'configuration' => - array ( - ), - 'features' => 30, - 'modes' => 3, - 'default' => true, - 'class' => 'cachestore_file', - 'lock' => 'cachelock_file_default', - ), - 'default_session' => - array ( - 'name' => 'default_session', - 'plugin' => 'session', - 'configuration' => - array ( - ), - 'features' => 14, - 'modes' => 2, - 'default' => true, - 'class' => 'cachestore_session', - 'lock' => 'cachelock_file_default', - ), - 'default_request' => - array ( - 'name' => 'default_request', - 'plugin' => 'static', - 'configuration' => - array ( - ), - 'features' => 31, - 'modes' => 4, - 'default' => true, - 'class' => 'cachestore_static', - 'lock' => 'cachelock_file_default', - ), - 'redis' => - array ( - 'name' => 'redis', - 'plugin' => 'redis', - 'configuration' => - array ( - 'server' => '$redisDns', - 'prefix' => 'moodle_prod', - 'password' => '$redisAuth', - 'serializer' => '1', - ), - 'features' => 26, - 'modes' => 3, - 'mappingsonly' => false, - 'class' => 'cachestore_redis', - 'default' => false, - 'lock' => 'cachelock_file_default', - ), - 'local_file' => - array ( - 'name' => 'local_file', - 'plugin' => 'file', - 'configuration' => - array ( - 'path' => '/tmp/muc/moodle_prod', - 'autocreate' => 1, - ), - 'features' => 30, - 'modes' => 3, - 'mappingsonly' => false, - 'class' => 'cachestore_file', - 'default' => false, - 'lock' => 'cachelock_file_default', - ), - ), - 'modemappings' => - array ( - 0 => - array ( - 'store' => 'redis', - 'mode' => 1, - 'sort' => 0, - ), - 1 => - array ( - 'store' => 'default_session', - 'mode' => 2, - 'sort' => 0, - ), - 2 => - array ( - 'store' => 'default_request', - 'mode' => 4, - 'sort' => 0, - ), - ), - 'definitions' => - array ( - 'core/string' => - array ( - 'mode' => 1, - 'simplekeys' => true, - 'simpledata' => true, - 'staticacceleration' => true, - 'staticaccelerationsize' => 30, - 'canuselocalstore' => true, - 'component' => 'core', - 'area' => 'string', - 'selectedsharingoption' => 2, - 'userinputsharingkey' => '', - 'sharingoptions' => 15, - ), - 'core/langmenu' => - array ( - 'mode' => 1, - 'simplekeys' => true, - 'simpledata' => true, - 'staticacceleration' => true, - 'canuselocalstore' => true, - 'component' => 'core', - 'area' => 'langmenu', - 'selectedsharingoption' => 2, - 'userinputsharingkey' => '', - 'sharingoptions' => 15, - ), - 'core/databasemeta' => - array ( - 'mode' => 1, - 'requireidentifiers' => - array ( - 0 => 'dbfamily', - ), - 'simpledata' => true, - 'staticacceleration' => true, - 'staticaccelerationsize' => 15, - 'component' => 'core', - 'area' => 'databasemeta', - 'selectedsharingoption' => 2, - 'userinputsharingkey' => '', - 'sharingoptions' => 15, - ), - 'core/eventinvalidation' => - array ( - 'mode' => 1, - 'staticacceleration' => true, - 'requiredataguarantee' => true, - 'simpledata' => true, - 'component' => 'core', - 'area' => 'eventinvalidation', - 'selectedsharingoption' => 2, - 'userinputsharingkey' => '', - 'sharingoptions' => 15, - ), - 'core/questiondata' => - array ( - 'mode' => 1, - 'simplekeys' => true, - 'requiredataguarantee' => false, - 'datasource' => 'question_finder', - 'datasourcefile' => 'question/engine/bank.php', - 'component' => 'core', - 'area' => 'questiondata', - 'selectedsharingoption' => 2, - 'userinputsharingkey' => '', - 'sharingoptions' => 15, - ), - 'core/htmlpurifier' => - array ( - 'mode' => 1, - 'canuselocalstore' => true, - 'component' => 'core', - 'area' => 'htmlpurifier', - 'selectedsharingoption' => 2, - 'userinputsharingkey' => '', - 'sharingoptions' => 15, - ), - 'core/config' => - array ( - 'mode' => 1, - 'staticacceleration' => true, - 'simpledata' => true, - 'component' => 'core', - 'area' => 'config', - 'selectedsharingoption' => 2, - 'userinputsharingkey' => '', - 'sharingoptions' => 15, - ), - 'core/groupdata' => - array ( - 'mode' => 1, - 'simplekeys' => true, - 'simpledata' => true, - 'staticacceleration' => true, - 'staticaccelerationsize' => 2, - 'component' => 'core', - 'area' => 'groupdata', - 'selectedsharingoption' => 2, - 'userinputsharingkey' => '', - 'sharingoptions' => 15, - ), - 'core/calendar_subscriptions' => - array ( - 'mode' => 1, - 'simplekeys' => true, - 'simpledata' => true, - 'staticacceleration' => true, - 'component' => 'core', - 'area' => 'calendar_subscriptions', - 'selectedsharingoption' => 2, - 'userinputsharingkey' => '', - 'sharingoptions' => 15, - ), - 'core/capabilities' => - array ( - 'mode' => 1, - 'simplekeys' => true, - 'simpledata' => true, - 'staticacceleration' => true, - 'staticaccelerationsize' => 1, - 'ttl' => 3600, - 'component' => 'core', - 'area' => 'capabilities', - 'selectedsharingoption' => 2, - 'userinputsharingkey' => '', - 'sharingoptions' => 15, - ), - 'core/yuimodules' => - array ( - 'mode' => 1, - 'component' => 'core', - 'area' => 'yuimodules', - 'selectedsharingoption' => 2, - 'userinputsharingkey' => '', - 'sharingoptions' => 15, - ), - 'core/observers' => - array ( - 'mode' => 1, - 'simplekeys' => true, - 'simpledata' => true, - 'staticacceleration' => true, - 'staticaccelerationsize' => 2, - 'component' => 'core', - 'area' => 'observers', - 'selectedsharingoption' => 2, - 'userinputsharingkey' => '', - 'sharingoptions' => 15, - ), - 'core/plugin_manager' => - array ( - 'mode' => 1, - 'simplekeys' => true, - 'simpledata' => true, - 'component' => 'core', - 'area' => 'plugin_manager', - 'selectedsharingoption' => 2, - 'userinputsharingkey' => '', - 'sharingoptions' => 15, - ), - 'core/coursecattree' => - array ( - 'mode' => 1, - 'staticacceleration' => true, - 'invalidationevents' => - array ( - 0 => 'changesincoursecat', - ), - 'component' => 'core', - 'area' => 'coursecattree', - 'selectedsharingoption' => 2, - 'userinputsharingkey' => '', - 'sharingoptions' => 15, - ), - 'core/coursecat' => - array ( - 'mode' => 2, - 'invalidationevents' => - array ( - 0 => 'changesincoursecat', - 1 => 'changesincourse', - ), - 'ttl' => 600, - 'component' => 'core', - 'area' => 'coursecat', - 'selectedsharingoption' => 2, - 'userinputsharingkey' => '', - 'sharingoptions' => 2, - ), - 'core/coursecatrecords' => - array ( - 'mode' => 4, - 'simplekeys' => true, - 'invalidationevents' => - array ( - 0 => 'changesincoursecat', - ), - 'component' => 'core', - 'area' => 'coursecatrecords', - 'selectedsharingoption' => 2, - 'userinputsharingkey' => '', - 'sharingoptions' => 2, - ), - 'core/coursecontacts' => - array ( - 'mode' => 1, - 'staticacceleration' => true, - 'simplekeys' => true, - 'ttl' => 3600, - 'component' => 'core', - 'area' => 'coursecontacts', - 'selectedsharingoption' => 2, - 'userinputsharingkey' => '', - 'sharingoptions' => 15, - ), - 'core/repositories' => - array ( - 'mode' => 4, - 'component' => 'core', - 'area' => 'repositories', - 'selectedsharingoption' => 2, - 'userinputsharingkey' => '', - 'sharingoptions' => 2, - ), - 'core/externalbadges' => - array ( - 'mode' => 1, - 'simplekeys' => true, - 'ttl' => 3600, - 'component' => 'core', - 'area' => 'externalbadges', - 'selectedsharingoption' => 2, - 'userinputsharingkey' => '', - 'sharingoptions' => 15, - ), - 'core/coursemodinfo' => - array ( - 'mode' => 1, - 'simplekeys' => true, - 'canuselocalstore' => true, - 'component' => 'core', - 'area' => 'coursemodinfo', - 'selectedsharingoption' => 2, - 'userinputsharingkey' => '', - 'sharingoptions' => 15, - ), - 'core/userselections' => - array ( - 'mode' => 2, - 'simplekeys' => true, - 'simpledata' => true, - 'component' => 'core', - 'area' => 'userselections', - 'selectedsharingoption' => 2, - 'userinputsharingkey' => '', - 'sharingoptions' => 2, - ), - 'core/completion' => - array ( - 'mode' => 1, - 'simplekeys' => true, - 'simpledata' => true, - 'ttl' => 3600, - 'staticacceleration' => true, - 'staticaccelerationsize' => 2, - 'component' => 'core', - 'area' => 'completion', - 'selectedsharingoption' => 2, - 'userinputsharingkey' => '', - 'sharingoptions' => 15, - ), - 'core/coursecompletion' => - array ( - 'mode' => 1, - 'simplekeys' => true, - 'simpledata' => true, - 'ttl' => 3600, - 'staticacceleration' => true, - 'staticaccelerationsize' => 30, - 'component' => 'core', - 'area' => 'coursecompletion', - 'selectedsharingoption' => 2, - 'userinputsharingkey' => '', - 'sharingoptions' => 15, - ), - 'core/navigation_expandcourse' => - array ( - 'mode' => 2, - 'simplekeys' => true, - 'simpledata' => true, - 'component' => 'core', - 'area' => 'navigation_expandcourse', - 'selectedsharingoption' => 2, - 'userinputsharingkey' => '', - 'sharingoptions' => 2, - ), - 'core/suspended_userids' => - array ( - 'mode' => 4, - 'simplekeys' => true, - 'simpledata' => true, - 'component' => 'core', - 'area' => 'suspended_userids', - 'selectedsharingoption' => 2, - 'userinputsharingkey' => '', - 'sharingoptions' => 2, - ), - 'core/roledefs' => - array ( - 'mode' => 1, - 'simplekeys' => true, - 'simpledata' => true, - 'staticacceleration' => true, - 'staticaccelerationsize' => 30, - 'component' => 'core', - 'area' => 'roledefs', - 'selectedsharingoption' => 2, - 'userinputsharingkey' => '', - 'sharingoptions' => 15, - ), - 'core/plugin_functions' => - array ( - 'mode' => 1, - 'simplekeys' => true, - 'simpledata' => true, - 'staticacceleration' => true, - 'staticaccelerationsize' => 5, - 'component' => 'core', - 'area' => 'plugin_functions', - 'selectedsharingoption' => 2, - 'userinputsharingkey' => '', - 'sharingoptions' => 15, - ), - 'core/tags' => - array ( - 'mode' => 4, - 'simplekeys' => true, - 'staticacceleration' => true, - 'component' => 'core', - 'area' => 'tags', - 'selectedsharingoption' => 2, - 'userinputsharingkey' => '', - 'sharingoptions' => 2, - ), - 'core/grade_categories' => - array ( - 'mode' => 2, - 'simplekeys' => true, - 'invalidationevents' => - array ( - 0 => 'changesingradecategories', - ), - 'component' => 'core', - 'area' => 'grade_categories', - 'selectedsharingoption' => 2, - 'userinputsharingkey' => '', - 'sharingoptions' => 2, - ), - 'core/temp_tables' => - array ( - 'mode' => 4, - 'simplekeys' => true, - 'simpledata' => true, - 'component' => 'core', - 'area' => 'temp_tables', - 'selectedsharingoption' => 2, - 'userinputsharingkey' => '', - 'sharingoptions' => 2, - ), - 'core/tagindexbuilder' => - array ( - 'mode' => 2, - 'simplekeys' => true, - 'simplevalues' => true, - 'staticacceleration' => true, - 'staticaccelerationsize' => 10, - 'ttl' => 900, - 'invalidationevents' => - array ( - 0 => 'resettagindexbuilder', - ), - 'component' => 'core', - 'area' => 'tagindexbuilder', - 'selectedsharingoption' => 2, - 'userinputsharingkey' => '', - 'sharingoptions' => 2, - ), - 'core/contextwithinsights' => - array ( - 'mode' => 1, - 'simplekeys' => true, - 'simpledata' => true, - 'staticacceleration' => true, - 'staticaccelerationsize' => 1, - 'component' => 'core', - 'area' => 'contextwithinsights', - 'selectedsharingoption' => 2, - 'userinputsharingkey' => '', - 'sharingoptions' => 15, - ), - 'core/message_processors_enabled' => - array ( - 'mode' => 1, - 'simplekeys' => true, - 'simpledata' => true, - 'staticacceleration' => true, - 'staticaccelerationsize' => 3, - 'component' => 'core', - 'area' => 'message_processors_enabled', - 'selectedsharingoption' => 2, - 'userinputsharingkey' => '', - 'sharingoptions' => 15, - ), - 'core/message_time_last_message_between_users' => - array ( - 'mode' => 1, - 'simplekeys' => true, - 'simplevalues' => true, - 'datasource' => '\\core_message\\time_last_message_between_users', - 'component' => 'core', - 'area' => 'message_time_last_message_between_users', - 'selectedsharingoption' => 2, - 'userinputsharingkey' => '', - 'sharingoptions' => 15, - ), - 'core/fontawesomeiconmapping' => - array ( - 'mode' => 1, - 'simplekeys' => true, - 'simpledata' => true, - 'staticacceleration' => true, - 'staticaccelerationsize' => 1, - 'component' => 'core', - 'area' => 'fontawesomeiconmapping', - 'selectedsharingoption' => 2, - 'userinputsharingkey' => '', - 'sharingoptions' => 15, - ), - 'core/postprocessedcss' => - array ( - 'mode' => 1, - 'simplekeys' => true, - 'simpledata' => true, - 'staticacceleration' => false, - 'component' => 'core', - 'area' => 'postprocessedcss', - 'selectedsharingoption' => 2, - 'userinputsharingkey' => '', - 'sharingoptions' => 15, - ), - 'core/user_group_groupings' => - array ( - 'mode' => 1, - 'simplekeys' => true, - 'simpledata' => true, - 'staticacceleration' => true, - 'component' => 'core', - 'area' => 'user_group_groupings', - 'selectedsharingoption' => 2, - 'userinputsharingkey' => '', - 'sharingoptions' => 15, - ), - 'availability_grade/scores' => - array ( - 'mode' => 1, - 'staticacceleration' => true, - 'staticaccelerationsize' => 2, - 'ttl' => 3600, - 'component' => 'availability_grade', - 'area' => 'scores', - 'selectedsharingoption' => 2, - 'userinputsharingkey' => '', - 'sharingoptions' => 15, - ), - 'availability_grade/items' => - array ( - 'mode' => 1, - 'staticacceleration' => true, - 'staticaccelerationsize' => 2, - 'ttl' => 3600, - 'component' => 'availability_grade', - 'area' => 'items', - 'selectedsharingoption' => 2, - 'userinputsharingkey' => '', - 'sharingoptions' => 15, - ), - 'mod_glossary/concepts' => - array ( - 'mode' => 1, - 'simplekeys' => true, - 'simpledata' => false, - 'staticacceleration' => true, - 'staticaccelerationsize' => 30, - 'component' => 'mod_glossary', - 'area' => 'concepts', - 'selectedsharingoption' => 2, - 'userinputsharingkey' => '', - 'sharingoptions' => 15, - ), - 'repository_googledocs/folder' => - array ( - 'mode' => 1, - 'simplekeys' => false, - 'simpledata' => true, - 'staticacceleration' => true, - 'staticaccelerationsize' => 10, - 'canuselocalstore' => true, - 'component' => 'repository_googledocs', - 'area' => 'folder', - 'selectedsharingoption' => 2, - 'userinputsharingkey' => '', - 'sharingoptions' => 15, - ), - 'repository_onedrive/folder' => - array ( - 'mode' => 1, - 'simplekeys' => false, - 'simpledata' => true, - 'staticacceleration' => true, - 'staticaccelerationsize' => 10, - 'canuselocalstore' => true, - 'component' => 'repository_onedrive', - 'area' => 'folder', - 'selectedsharingoption' => 2, - 'userinputsharingkey' => '', - 'sharingoptions' => 15, - ), - 'repository_skydrive/foldername' => - array ( - 'mode' => 2, - 'component' => 'repository_skydrive', - 'area' => 'foldername', - 'selectedsharingoption' => 2, - 'userinputsharingkey' => '', - 'sharingoptions' => 2, - ), - 'tool_mobile/plugininfo' => - array ( - 'mode' => 1, - 'simplekeys' => true, - 'staticacceleration' => true, - 'staticaccelerationsize' => 1, - 'component' => 'tool_mobile', - 'area' => 'plugininfo', - 'selectedsharingoption' => 2, - 'userinputsharingkey' => '', - 'sharingoptions' => 15, - ), - 'tool_monitor/eventsubscriptions' => - array ( - 'mode' => 1, - 'simplekeys' => true, - 'simpledata' => true, - 'staticacceleration' => true, - 'staticaccelerationsize' => 10, - 'component' => 'tool_monitor', - 'area' => 'eventsubscriptions', - 'selectedsharingoption' => 2, - 'userinputsharingkey' => '', - 'sharingoptions' => 15, - ), - 'tool_uploadcourse/helper' => - array ( - 'mode' => 4, - 'component' => 'tool_uploadcourse', - 'area' => 'helper', - 'selectedsharingoption' => 2, - 'userinputsharingkey' => '', - 'sharingoptions' => 2, - ), - 'tool_usertours/tourdata' => - array ( - 'mode' => 1, - 'simplekeys' => true, - 'simpledata' => true, - 'staticacceleration' => true, - 'staticaccelerationsize' => 1, - 'component' => 'tool_usertours', - 'area' => 'tourdata', - 'selectedsharingoption' => 2, - 'userinputsharingkey' => '', - 'sharingoptions' => 15, - ), - 'tool_usertours/stepdata' => - array ( - 'mode' => 1, - 'simplekeys' => true, - 'simpledata' => true, - 'staticacceleration' => true, - 'staticaccelerationsize' => 1, - 'component' => 'tool_usertours', - 'area' => 'stepdata', - 'selectedsharingoption' => 2, - 'userinputsharingkey' => '', - 'sharingoptions' => 15, - ), - ), - 'definitionmappings' => - array ( - 0 => - array ( - 'store' => 'local_file', - 'definition' => 'core/coursemodinfo', - 'sort' => 1, - ), - 1 => - array ( - 'store' => 'redis', - 'definition' => 'core/groupdata', - 'sort' => 1, - ), - 2 => - array ( - 'store' => 'redis', - 'definition' => 'core/roledefs', - 'sort' => 1, - ), - 3 => - array ( - 'store' => 'redis', - 'definition' => 'tool_usertours/tourdata', - 'sort' => 1, - ), - 4 => - array ( - 'store' => 'redis', - 'definition' => 'repository_onedrive/folder', - 'sort' => 1, - ), - 5 => - array ( - 'store' => 'redis', - 'definition' => 'core/message_processors_enabled', - 'sort' => 1, - ), - 6 => - array ( - 'store' => 'redis', - 'definition' => 'core/coursecontacts', - 'sort' => 1, - ), - 7 => - array ( - 'store' => 'redis', - 'definition' => 'repository_googledocs/folder', - 'sort' => 1, - ), - 8 => - array ( - 'store' => 'redis', - 'definition' => 'core/questiondata', - 'sort' => 1, - ), - 9 => - array ( - 'store' => 'redis', - 'definition' => 'core/coursecat', - 'sort' => 1, - ), - 10 => - array ( - 'store' => 'redis', - 'definition' => 'core/databasemeta', - 'sort' => 1, - ), - 11 => - array ( - 'store' => 'redis', - 'definition' => 'core/eventinvalidation', - 'sort' => 1, - ), - 12 => - array ( - 'store' => 'redis', - 'definition' => 'core/coursecattree', - 'sort' => 1, - ), - 13 => - array ( - 'store' => 'redis', - 'definition' => 'core/coursecompletion', - 'sort' => 1, - ), - 14 => - array ( - 'store' => 'redis', - 'definition' => 'core/user_group_groupings', - 'sort' => 1, - ), - 15 => - array ( - 'store' => 'redis', - 'definition' => 'core/capabilities', - 'sort' => 1, - ), - 16 => - array ( - 'store' => 'redis', - 'definition' => 'core/yuimodules', - 'sort' => 1, - ), - 17 => - array ( - 'store' => 'redis', - 'definition' => 'core/observers', - 'sort' => 1, - ), - 18 => - array ( - 'store' => 'redis', - 'definition' => 'mod_glossary/concepts', - 'sort' => 1, - ), - 19 => - array ( - 'store' => 'redis', - 'definition' => 'core/fontawesomeiconmapping', - 'sort' => 1, - ), - 20 => - array ( - 'store' => 'redis', - 'definition' => 'core/config', - 'sort' => 1, - ), - 21 => - array ( - 'store' => 'redis', - 'definition' => 'tool_mobile/plugininfo', - 'sort' => 1, - ), - 22 => - array ( - 'store' => 'redis', - 'definition' => 'core/plugin_functions', - 'sort' => 1, - ), - 23 => - array ( - 'store' => 'redis', - 'definition' => 'core/postprocessedcss', - 'sort' => 1, - ), - 24 => - array ( - 'store' => 'redis', - 'definition' => 'core/plugin_manager', - 'sort' => 1, - ), - 25 => - array ( - 'store' => 'redis', - 'definition' => 'tool_usertours/stepdata', - 'sort' => 1, - ), - 26 => - array ( - 'store' => 'redis', - 'definition' => 'availability_grade/items', - 'sort' => 1, - ), - 27 => - array ( - 'store' => 'local_file', - 'definition' => 'core/string', - 'sort' => 1, - ), - 28 => - array ( - 'store' => 'redis', - 'definition' => 'core/externalbadges', - 'sort' => 1, - ), - 29 => - array ( - 'store' => 'local_file', - 'definition' => 'core/langmenu', - 'sort' => 1, - ), - 30 => - array ( - 'store' => 'local_file', - 'definition' => 'core/htmlpurifier', - 'sort' => 1, - ), - 31 => - array ( - 'store' => 'redis', - 'definition' => 'core/completion', - 'sort' => 1, - ), - 32 => - array ( - 'store' => 'redis', - 'definition' => 'core/calendar_subscriptions', - 'sort' => 1, - ), - 33 => - array ( - 'store' => 'redis', - 'definition' => 'core/contextwithinsights', - 'sort' => 1, - ), - 34 => - array ( - 'store' => 'redis', - 'definition' => 'tool_monitor/eventsubscriptions', - 'sort' => 1, - ), - 35 => - array ( - 'store' => 'redis', - 'definition' => 'core/message_time_last_message_between_users', - 'sort' => 1, - ), - 36 => - array ( - 'store' => 'redis', - 'definition' => 'availability_grade/scores', - 'sort' => 1, - ), - ), - 'locks' => - array ( - 'cachelock_file_default' => - array ( - 'name' => 'cachelock_file_default', - 'type' => 'cachelock_file', - 'dir' => 'filelocks', - 'default' => true, - ), - ), -); -EOF - - # redis configuration in /moodle/html/moodle/config.php - sed -i "23 a \$CFG->session_redis_lock_expire = 7200;" /moodle/html/moodle/config.php - sed -i "23 a \$CFG->session_redis_acquire_lock_timeout = 120;" /moodle/html/moodle/config.php - sed -i "23 a \$CFG->session_redis_prefix = 'moodle_prod'; // Optional, default is don't set one." /moodle/html/moodle/config.php - sed -i "23 a \$CFG->session_redis_database = 0; // Optional, default is db 0." /moodle/html/moodle/config.php - sed -i "23 a \$CFG->session_redis_port = 6379; // Optional." /moodle/html/moodle/config.php - sed -i "23 a \$CFG->session_redis_host = '$redisDns';" /moodle/html/moodle/config.php - sed -i "23 a \$CFG->session_redis_auth = '$redisAuth';" /moodle/html/moodle/config.php - sed -i "23 a \$CFG->session_handler_class = '\\\core\\\session\\\redis';" /moodle/html/moodle/config.php - - # We proxy ssl, so moodle needs to know this - sed -i "23 a \$CFG->sslproxy = 'true';" /moodle/html/moodle/config.php - - # Set up elasticsearch plugin - sed -i "23 a \$CFG->forced_plugin_settings = ['search_elastic' => ['hostname' => 'http://$elasticVm1IP']];" /moodle/html/moodle/config.php - sed -i "23 a \$CFG->searchengine = 'elastic';" /moodle/html/moodle/config.php - sed -i "23 a \$CFG->enableglobalsearch = 'true';" /moodle/html/moodle/config.php - - # Set the ObjectFS alternate filesystem - sed -i "23 a \$CFG->alternative_file_system_class = '\\\tool_objectfs\\\azure_file_system';" /moodle/html/moodle/config.php - - # Get a new version of Postgres to match Azure version - add-apt-repository "deb http://apt.postgresql.org/pub/repos/apt/ xenial-pgdg main" - wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | sudo apt-key add - - apt-get update - apt-get install postgresql-client-9.6 - - # Set up cronned sql dump - cat < /etc/cron.d/sql-backup -22 02 * * * root /usr/bin/pg_dump -Fc -h $postgresIP -U ${azuremoodledbuser} ${moodledbname} > /moodle/db-backup.sql -EOF - - - # Turning off services we don't need the jumpbox running - service nginx stop - service php7.0-fpm stop - service varnish stop - service varnishncsa stop - service varnishlog stop - - # make sure Moodle can read its code directory but not write - sudo chown -R root.root /moodle/html/moodle - sudo find /moodle/html/moodle -type f -exec chmod 644 '{}' \; - sudo find /moodle/html/moodle -type d -exec chmod 755 '{}' \; - -} > /tmp/install.log diff --git a/scripts/setup_nfs_ha.sh b/scripts/setup_nfs_ha.sh new file mode 100644 index 00000000..d879d02a --- /dev/null +++ b/scripts/setup_nfs_ha.sh @@ -0,0 +1,256 @@ +#!/bin/bash +# +# Script to set up highly available NFS server on an Ubuntu 16.04 (or higher) VM +# that should be used on Azure with the custom script extension (which runs this script as root) +# + +set -e + +# Parameters +NODE1NAME=$1 +NODE1IP=$2 +NODE2NAME=$3 +NODE2IP=$4 +NFS_CLIENTS_IP_RANGE=$5 # E.g., "10.0.0.0/24". Can be "*", but strongly discouraged + +# This VM's IP address, to detect if this VM should be the master (Node 1 is the initial master) +MY_IP=$(hostname -i) + +. ./helper_functions.sh + +function setup_required_packages +{ + apt update + apt -y install build-essential autoconf flex nfs-kernel-server corosync pacemaker resource-agents + + # Shouldn't let systemd start nfs-kernel-server (Pacemaker should do that) + systemctl stop nfs-kernel-server + systemctl disable nfs-kernel-server + + # Setup static port assignments for mountd, statd, quotad, nlm (tcp), and nlm (udp) respectively: + sed -i 's/^\(RPCMOUNTDOPTS="--manage-gids\)"/\1 -p 2000"/g' /etc/default/nfs-kernel-server + sed -i 's/^STATDOPTS=.*$/STATDOPTS="--port 2001 --outgoing-port 2002"/' /etc/default/nfs-common + if [ -f /etc/default/quota ]; then + sed -i 's/^RPCQUOTADOPTS=.*$/RPCQUOTADOPTS="-p 2003"/' /etc/default/quota + fi + cat < /etc/modprobe.d/azmdl-nfs-ports.conf +options lockd nlm_udpport=2004 nlm_tcpport=2004 +options nfs callback_tcpport=2005 +EOF + + cat < /etc/sysctl.d/30-azmdl-nfs-ports.conf +fs.nfs.nlm_tcpport=2004 +fs.nfs.nlm_udpport=2004 +EOF + # Reread modified sysctl settings for modified NFS static ports + sysctl --system + + # Above alone still doesn't work for static ports. Try restarting related services. + systemctl try-restart nfs-config.service rpcbind.service rpc-statd.service nfs-server.service + + # We need to install the "azure-lb" command separately if the resource-agents package didn't have it. + pushd /usr/lib/ocf/resource.d/heartbeat + if [ ! -e azure-lb ]; then + curl -LO https://raw.githubusercontent.com/ClusterLabs/resource-agents/master/heartbeat/azure-lb + chmod +x ./azure-lb + fi + popd +} + +function setup_drbd_module_and_tools +{ + # We currently have to build the DRBD kernel module, as it's not included + # in the default linux-azure kernels or in any Azure extra packages. + # We should use the packaged DRBD module once Azure starts releasing + # extra modules packages that include DRBD module. + + pushd /tmp + git clone http://github.com/LINBIT/drbd-9.0 + git clone http://github.com/LINBIT/drbd-utils + cd drbd-9.0 + make && make install + modprobe drbd + cd ../drbd-utils + ./autogen.sh + ./configure --prefix=/usr --localstatedir=/var --sysconfdir=/etc + make tools && make install-tools + cd .. + rm -rf drbd-9.0 drbd-utils + popd +} + +function setup_drbd_with_disk +{ + local disk=$1 + local node1name=$2 + local node1ip=$3 + local node2name=$4 + local node2ip=$5 + local drbd_resource_name=$6 + local drbd_device_path=$7 + local drbd_device_mount_point=$8 + + # Put LVM (to gain more flexibility) on the whole disk + local vgname=drbdvg + local lvname=drbdlv + wipefs -af $disk + pvcreate $disk + vgcreate $vgname $disk + lvcreate -n $lvname -l 95%VG $vgname + + # Set up DRBD config + cat < /etc/drbd.d/${drbd_resource_name}.res +resource $drbd_resource_name { + device ${drbd_device_path}; + disk /dev/${vgname}/${lvname}; + meta-disk internal; + disk { + c-fill-target 1M; + c-max-rate 110M; + c-min-rate 120K; + } + net { + max-buffers 20k; + } + on ${node1name} { + address ${node1ip}:7789; + } + on ${node2name} { + address ${node2ip}:7789; + } +} +EOF + + # Initialize DRBD's metadata and bring up the device on both nodes + drbdadm create-md $drbd_resource_name + drbdadm up $drbd_resource_name + drbdadm status + + # On the master (initially node 1) only, force DRBD into Primary and create an ext4 file system on the DRBD device + if [ "$MY_IP" = "$NODE1IP" ]; then + drbdadm primary $drbd_resource_name --force + mkfs.ext4 $drbd_device_path + mkdir -p $drbd_device_mount_point && mount $drbd_device_path $drbd_device_mount_point + fi +} + +function setup_corosync_and_pacemaker_for_nfs +{ + local node1ip=$1 + local node2ip=$2 + local drbd_resource_name=$3 # E.g., azmdlr0 + local drbd_device_path=$4 # E.g., /dev/drbd0 + local drbd_mount_point=$5 # E.g., /drbd + local nfs_export_path=$6 # E.g., /drbd/moodle + local nfs_client_spec=$7 # E.g., * or 10.11.22.0/24 + + mv /etc/corosync/corosync.conf /etc/corosync/corosync.conf.orig || true + + local cluster_name=azmdl-cluster + + cat < /etc/corosync/corosync.conf +totem { + version: 2 + secauth: off + cluster_name: ${cluster_name} + transport: udpu +} + +nodelist { + node { + ring0_addr: ${node1ip} + nodeid: 1 + } + node { + ring0_addr: ${node2ip} + nodeid: 2 + } +} + +quorum { + provider: corosync_votequorum + two_node:1 +} + +logging { + to_syslog: yes +} +EOF + + systemctl enable corosync pacemaker + systemctl restart corosync pacemaker + + # TODO Should confirm if 'corosync-cfgtool -s' gives a non-loopback IP address (not 127.0.0.1), e.g.: + # $ corosync-cfgtool -s + # Printing ring status. + # Local node ID 2 + # RING ID 0 + # id = 10.0.0.5 + # status = ring 0 active with no faults + + # Finally, configure Pacemaker cluster resources, only on the initial master + if [ "$MY_IP" = "$node1ip" ]; then + mkdir -p ${nfs_export_path} + crm configure <> /tmp/vars.txt echo $glusterVolume >> /tmp/vars.txt echo $siteFQDN >> /tmp/vars.txt +echo $httpsTermination >> /tmp/vars.txt +echo $syslogServer >> /tmp/vars.txt +echo $webServerType >> /tmp/vars.txt +echo $dbServerType >> /tmp/vars.txt +echo $fileServerType >> /tmp/vars.txt +echo $storageAccountName >> /tmp/vars.txt +echo $storageAccountKey >> /tmp/vars.txt +echo $nfsVmName >> /tmp/vars.txt +echo $nfsByoIpExportPath >> /tmp/vars.txt +echo $htmlLocalCopySwitch >> /tmp/vars.txt +echo $phpVersion >> /tmp/vars.txt + +# downloading and updating php packages from the repository +# sudo dpkg --configure –a + sudo add-apt-repository ppa:ondrej/php -y > /dev/null 2>&1 + sudo apt-get update > /dev/null 2>&1 + +check_fileServerType_param $fileServerType { # make sure the system does automatic update @@ -37,27 +57,79 @@ echo $siteFQDN >> /tmp/vars.txt sudo apt-get -y install unattended-upgrades # install pre-requisites - sudo apt-get -y install python-software-properties unzip rsyslog - - #configure gluster repository & install gluster client - sudo add-apt-repository ppa:gluster/glusterfs-3.8 -y - sudo apt-get -y update - sudo apt-get -y install glusterfs-client postgresql-client mysql-client git - + # sudo apt-get -y install python-software-properties unzip rsyslog + sudo apt-get -y install software-properties-common + sudo apt-get -y install unzip + sudo apt-get -y install rsyslog + sudo apt-get -y install postgresql-client mysql-client git + + if [ $fileServerType = "gluster" ]; then + #configure gluster repository & install gluster client + sudo add-apt-repository ppa:gluster/glusterfs-3.10 -y + sudo apt-get -y update + sudo apt-get -y install glusterfs-client + elif [ "$fileServerType" = "azurefiles" ]; then + sudo apt-get -y install cifs-utils + fi + # install the base stack - sudo apt-get -y install nginx php-fpm varnish php php-cli php-curl php-zip - + # passing php versions $phpVersion + sudo apt-get -y install varnish php$phpVersion php$phpVersion-cli php$phpVersion-curl php$phpVersion-zip php-pear php$phpVersion-mbstring php$phpVersion-dev mcrypt + + # if webservertype is nginx then apache2 will be masked. + # service=apache2 + # if [ "$webServerType" = "nginx" ]; then + # if [ $(ps -ef | grep -v grep | grep $service | wc -l) > 0 ]; then + # echo “Stop the $service!!!” + # sudo systemctl stop $service + # sudo systemctl mask $service + # fi + # fi + + if [ "$webServerType" = "nginx" -o "$httpsTermination" = "VMSS" ]; then + sudo apt-get -y install nginx + fi + + if [ "$webServerType" = "apache" ]; then + # install apache pacakges + sudo apt-get -y install apache2 libapache2-mod-php + else + # for nginx-only option + sudo apt-get -y install php$phpVersion-fpm + fi + # Moodle requirements - sudo apt-get install -y graphviz aspell php-soap php-json php-redis php-bcmath php-gd php-pgsql php-mysql php-xmlrpc php-intl php-xml php-bz2 - - # Mount gluster fs for /moodle - sudo mkdir -p /moodle - sudo chown www-data /moodle - sudo chmod 770 /moodle - sudo echo -e 'mount -t glusterfs '$glusterNode':/'$glusterVolume' /moodle' - sudo mount -t glusterfs $glusterNode:/$glusterVolume /moodle - sudo echo -e $glusterNode':/'$glusterVolume' /moodle glusterfs defaults,_netdev,log-level=WARNING,log-file=/var/log/gluster.log 0 0' >> /etc/fstab - sudo mount -a + sudo apt-get install -y graphviz aspell php$phpVersion-soap php$phpVersion-json php$phpVersion-redis php$phpVersion-bcmath php$phpVersion-gd php$phpVersion-pgsql php$phpVersion-mysql php$phpVersion-xmlrpc php$phpVersion-intl php$phpVersion-xml php$phpVersion-bz2 + if [ "$dbServerType" = "mssql" ]; then + install_php_mssql_driver + + fi + + # PHP Version + PhpVer=$(get_php_version) + + if [ $fileServerType = "gluster" ]; then + # Mount gluster fs for /moodle + sudo mkdir -p /moodle + sudo chown www-data /moodle + sudo chmod 770 /moodle + sudo echo -e 'Adding Gluster FS to /etc/fstab and mounting it' + setup_and_mount_gluster_moodle_share $glusterNode $glusterVolume + elif [ $fileServerType = "nfs" ]; then + # mount NFS export (set up on controller VM--No HA) + echo -e '\n\rMounting NFS export from '$nfsVmName':/moodle on /moodle and adding it to /etc/fstab\n\r' + configure_nfs_client_and_mount $nfsVmName /moodle /moodle + elif [ $fileServerType = "nfs-ha" ]; then + # mount NFS-HA export + echo -e '\n\rMounting NFS export from '$nfsHaLbIP':'$nfsHaExportPath' on /moodle and adding it to /etc/fstab\n\r' + configure_nfs_client_and_mount $nfsHaLbIP $nfsHaExportPath /moodle + elif [ $fileServerType = "nfs-byo" ]; then + # mount NFS-BYO export + echo -e '\n\rMounting NFS export from '$nfsByoIpExportPath' on /moodle and adding it to /etc/fstab\n\r' + configure_nfs_client_and_mount0 $nfsByoIpExportPath /moodle + else # "azurefiles" + setup_and_mount_azure_files_moodle_share $storageAccountName $storageAccountKey + fi # Configure syslog to forward cat <> /etc/rsyslog.conf @@ -65,13 +137,14 @@ echo $siteFQDN >> /tmp/vars.txt \$UDPServerRun 514 EOF cat <> /etc/rsyslog.d/40-remote.conf -local1.* @${syslogserver}:514 -local2.* @${syslogserver}:514 +local1.* @${syslogServer}:514 +local2.* @${syslogServer}:514 EOF service syslog restart - # Build nginx config - cat < /etc/nginx/nginx.conf + if [ "$webServerType" = "nginx" -o "$httpsTermination" = "VMSS" ]; then + # Build nginx config + cat < /etc/nginx/nginx.conf user www-data; worker_processes 2; pid /run/nginx.pid; @@ -101,8 +174,11 @@ http { set_real_ip_from 127.0.0.1; real_ip_header X-Forwarded-For; - ssl_protocols TLSv1 TLSv1.1 TLSv1.2; # Dropping SSLv3, ref: POODLE - ssl_prefer_server_ciphers on; + #upgrading to TLSv1.2 and droping 1 & 1.1 + ssl_protocols TLSv1.2; + #ssl_prefer_server_ciphers on; + #adding ssl ciphers + 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; gzip on; gzip_disable "msie6"; @@ -112,13 +188,18 @@ http { gzip_buffers 16 8k; gzip_http_version 1.1; gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript; - +EOF + if [ "$httpsTermination" != "None" ]; then + cat <> /etc/nginx/nginx.conf map \$http_x_forwarded_proto \$fastcgi_https { default \$https; http ''; https on; - } + } +EOF + fi + cat <> /etc/nginx/nginx.conf log_format moodle_combined '\$remote_addr - \$upstream_http_x_moodleuser [\$time_local] ' '"\$request" \$status \$body_bytes_sent ' '"\$http_referer" "\$http_user_agent"'; @@ -128,12 +209,65 @@ http { include /etc/nginx/sites-enabled/*; } EOF + fi # if [ "$webServerType" = "nginx" -o "$httpsTermination" = "VMSS" ]; + + # Set up html dir local copy if specified + htmlRootDir="/moodle/html/moodle" + if [ "$htmlLocalCopySwitch" = "true" ]; then + mkdir -p /var/www/html + rsync -av --delete /moodle/html/moodle /var/www/html + htmlRootDir="/var/www/html/moodle" + setup_html_local_copy_cron_job + fi + + if [ "$httpsTermination" = "VMSS" ]; then + # Configure nginx/https + cat <> /etc/nginx/sites-enabled/${siteFQDN}.conf +server { + listen 443 ssl; + root ${htmlRootDir}; + index index.php index.html index.htm; + + ssl on; + ssl_certificate /moodle/certs/nginx.crt; + ssl_certificate_key /moodle/certs/nginx.key; + + # Log to syslog + error_log syslog:server=localhost,facility=local1,severity=error,tag=moodle; + access_log syslog:server=localhost,facility=local1,severity=notice,tag=moodle moodle_combined; + # Log XFF IP instead of varnish + set_real_ip_from 10.0.0.0/8; + set_real_ip_from 127.0.0.1; + set_real_ip_from 172.16.0.0/12; + set_real_ip_from 192.168.0.0/16; + real_ip_header X-Forwarded-For; + real_ip_recursive on; + + location / { + proxy_set_header Host \$host; + proxy_set_header HTTP_REFERER \$http_referer; + proxy_set_header X-Forwarded-Host \$host; + proxy_set_header X-Forwarded-Server \$host; + proxy_set_header X-Forwarded-Proto https; + proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for; + proxy_pass http://localhost:80; + + proxy_connect_timeout 3600; + proxy_send_timeout 3600; + proxy_read_timeout 3600; + send_timeout 3600; + } +} +EOF + fi + + if [ "$webServerType" = "nginx" ]; then cat <> /etc/nginx/sites-enabled/${siteFQDN}.conf server { listen 81 default; server_name ${siteFQDN}; - root /moodle/html/moodle; + root ${htmlRootDir}; index index.php index.html index.htm; # Log to syslog @@ -147,15 +281,17 @@ server { set_real_ip_from 192.168.0.0/16; real_ip_header X-Forwarded-For; real_ip_recursive on; - - +EOF + if [ "$httpsTermination" != "None" ]; then + cat <> /etc/nginx/sites-enabled/${siteFQDN}.conf # Redirect to https if (\$http_x_forwarded_proto != https) { return 301 https://\$server_name\$request_uri; } rewrite ^/(.*\.php)(/)(.*)$ /\$1?file=/\$3 last; - - +EOF + fi + cat <> /etc/nginx/sites-enabled/${siteFQDN}.conf # Filter out php-fpm status page location ~ ^/server-status { return 404; @@ -174,48 +310,63 @@ server { fastcgi_buffers 16 16k; fastcgi_buffer_size 32k; fastcgi_param SCRIPT_FILENAME \$document_root\$fastcgi_script_name; - fastcgi_pass unix:/run/php/php7.0-fpm.sock; + fastcgi_pass unix:/run/php/php${PhpVer}-fpm.sock; fastcgi_read_timeout 3600; fastcgi_index index.php; include fastcgi_params; } } -server { - listen 443 ssl; - root /moodle/html/moodle; - index index.php index.html index.htm; +EOF + fi # if [ "$webServerType" = "nginx" ]; - ssl on; - ssl_certificate /moodle/certs/nginx.crt; - ssl_certificate_key /moodle/certs/nginx.key; + if [ "$webServerType" = "apache" ]; then + # Configure Apache/php + sed -i "s/Listen 80/Listen 81/" /etc/apache2/ports.conf + a2enmod rewrite && a2enmod remoteip && a2enmod headers - # Log to syslog - error_log syslog:server=localhost,facility=local1,severity=error,tag=moodle; - access_log syslog:server=localhost,facility=local1,severity=notice,tag=moodle moodle_combined; + cat <> /etc/apache2/sites-enabled/${siteFQDN}.conf + + ServerName ${siteFQDN} - # Log XFF IP instead of varnish - set_real_ip_from 10.0.0.0/8; - set_real_ip_from 127.0.0.1; - set_real_ip_from 172.16.0.0/12; - set_real_ip_from 192.168.0.0/16; - real_ip_header X-Forwarded-For; - real_ip_recursive on; + ServerAdmin webmaster@localhost + DocumentRoot ${htmlRootDir} - location / { - proxy_set_header Host \$host; - proxy_set_header HTTP_REFERER \$http_referer; - proxy_set_header X-Forwarded-Host \$host; - proxy_set_header X-Forwarded-Server \$host; - proxy_set_header X-Forwarded-Proto https; - proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for; - proxy_pass http://localhost:80; - } -} + + Options FollowSymLinks + AllowOverride All + Require all granted + EOF + if [ "$httpsTermination" != "None" ]; then + cat <> /etc/apache2/sites-enabled/${siteFQDN}.conf + # Redirect unencrypted direct connections to HTTPS + + RewriteEngine on + RewriteCond %{HTTP:X-Forwarded-Proto} !https [NC] + RewriteRule ^ https://%{SERVER_NAME}%{REQUEST_URI} [L,R=301] + +EOF + fi + cat <> /etc/apache2/sites-enabled/${siteFQDN}.conf + # Log X-Forwarded-For IP address instead of varnish (127.0.0.1) + SetEnvIf X-Forwarded-For "^.*\..*\..*\..*" forwarded + LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined + LogFormat "%{X-Forwarded-For}i %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" forwarded + ErrorLog "|/usr/bin/logger -t moodle -p local1.error" + CustomLog "|/usr/bin/logger -t moodle -p local1.notice" combined env=!forwarded + CustomLog "|/usr/bin/logger -t moodle -p local1.notice" forwarded env=forwarded + + +EOF + fi # if [ "$webServerType" = "apache" ]; # php config - PhpIni=/etc/php/7.0/fpm/php.ini + if [ "$webServerType" = "apache" ]; then + PhpIni=/etc/php/${PhpVer}/apache2/php.ini + else + PhpIni=/etc/php/${PhpVer}/fpm/php.ini + fi sed -i "s/memory_limit.*/memory_limit = 512M/" $PhpIni sed -i "s/max_execution_time.*/max_execution_time = 18000/" $PhpIni sed -i "s/max_input_vars.*/max_input_vars = 100000/" $PhpIni @@ -232,16 +383,24 @@ EOF # Remove the default site. Moodle is the only site we want rm -f /etc/nginx/sites-enabled/default - - # restart Nginx - sudo service nginx restart - - # fpm config - overload this - cat < /etc/php/7.0/fpm/pool.d/www.conf + if [ "$webServerType" = "apache" ]; then + rm -f /etc/apache2/sites-enabled/000-default.conf + fi + + if [ "$webServerType" = "nginx" -o "$httpsTermination" = "VMSS" ]; then + # update startup script to wait for certificate in /moodle mount + setup_moodle_mount_dependency_for_systemd_service nginx || exit 1 + # restart Nginx + sudo service nginx restart + fi + + if [ "$webServerType" = "nginx" ]; then + # fpm config - overload this + cat < /etc/php/${PhpVer}/fpm/pool.d/www.conf [www] user = www-data group = www-data -listen = /run/php/php7.0-fpm.sock +listen = /run/php/php${PhpVer}-fpm.sock listen.owner = www-data listen.group = www-data pm = dynamic @@ -251,8 +410,16 @@ pm.min_spare_servers = 20 pm.max_spare_servers = 30 EOF - # Restart fpm - service php7.0-fpm restart + # Restart fpm + service php${PhpVer}-fpm restart + fi + + if [ "$webServerType" = "apache" ]; then + if [ "$htmlLocalCopySwitch" != "true" ]; then + setup_moodle_mount_dependency_for_systemd_service apache2 || exit 1 + fi + sudo service apache2 restart + fi # Configure varnish startup for 16.04 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" @@ -502,6 +669,15 @@ sub vcl_synth { } EOF +# This code is stop apache2 which is installing in 18.04 + service=apache2 + if [ "$webServerType" = "nginx" ]; then + if [ $(ps -ef | grep -v grep | grep $service | wc -l) > 0 ]; then + echo “Stop the $service!!!” + sudo systemctl stop $service + sudo systemctl mask $service + fi + fi # Restart Varnish systemctl daemon-reload service varnish restart