├── .gitignore ├── .vscode └── launch.json ├── CODE-OF-CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE.txt ├── README.md ├── app.js ├── broker ├── Dockerfile ├── aclfile.conf ├── docker-build.sh ├── docker-run.sh ├── install-broker.sh ├── mosquitto.conf └── setpwd.sh ├── brokeragent ├── Dockerfile ├── aclfile.conf ├── docker-build.sh ├── docker-run.sh ├── install-prereqs.sh ├── mosquitto.conf └── setpwd.sh ├── client ├── README.md ├── args-util.js ├── clean.js ├── cmdport.js ├── controller-unittest.js ├── controller.js ├── index.html ├── libs │ ├── mdparser.js │ ├── spawner.js │ └── utils.js ├── messageprocessor.js ├── package.json ├── run-coldstart-all.ps1 ├── run-coldstart.config.json ├── run-coldstart.ps1 ├── test │ ├── coldstart │ │ ├── README.md │ │ └── run-coldstart.md │ ├── run-helloworldmvc-env.json │ ├── run-helloworldmvc.md │ ├── run-job-helloworldmvc.json │ ├── run-job-musicstore.json │ ├── run-localhost-env.json │ ├── run-localhost.js │ ├── run-localhost.md │ ├── run-musicstore-env.json │ ├── run-musicstore.md │ ├── run-spawner.js │ └── testenv.json └── web │ ├── agent.js │ ├── mqttws31.js │ ├── scripts │ ├── jquery-2.2.3.min.js │ └── knockout-3.3.0.js │ └── shoot.css ├── config.js ├── flows ├── .gitignore ├── flows_Dispatcher.json └── flows_Dispatcher_cred.json ├── infra ├── AzureTestCellDeployment │ ├── 1Linux_CellDeployment.json │ ├── 1Windows_CellDeployment.json │ ├── Deploy-1Linux-TestCell.ps1 │ ├── Deploy-1Win-TestCell.ps1 │ ├── Deploy-MultiRegion-TestCell.ps1 │ ├── Deploy-WinLinux-TestCell.ps1 │ ├── Deploy-WinWin-TestCell.ps1 │ ├── LinuxSetup.ssh │ ├── MultiRegion_Linux_CellDeployment.json │ ├── MultiRegion_Windows_CellDeployment.json │ ├── Windows-Linux_CellDeployment.json │ ├── Windows-Windows_CellDeployment.json │ └── WindowsVmSetup-Core.ps1 ├── AzureTestCellManagement │ └── ShutDownVMsInRescourceGroup.ps1 └── README.md ├── jsconfig.json ├── libs ├── credUtil.js ├── env.js ├── ipUtil.js ├── log.js ├── objUtil.js ├── recover.js ├── setenvNode.js └── setupNode.js ├── package.json ├── scripts ├── Dockerfile ├── Dockerfile.debug ├── Install.ps1 ├── Pack.ps1 ├── autoLogon.ps1 ├── cmdport ├── docker-debug.sh ├── docker-run.sh ├── install.sh ├── install_autoStart.ps1 └── write_version.sh ├── setup.js └── tools └── pull /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 18 | .grunt 19 | 20 | # node-waf configuration 21 | .lock-wscript 22 | 23 | # Compiled binary addons (http://nodejs.org/api/addons.html) 24 | build/Release 25 | 26 | # Dependency directory 27 | node_modules 28 | 29 | # Optional npm cache directory 30 | .npm 31 | 32 | # Optional REPL history 33 | .node_repl_history 34 | 35 | #Exclude credentials 36 | _creds.json 37 | _creds.js 38 | 39 | #exclude environment variables 40 | _envVars.json 41 | _logdir.json 42 | 43 | #Exclude logs 44 | logs/ 45 | logslink 46 | artifacts/ 47 | 48 | # VSCode stuff 49 | .BROWSE.VC.DB* 50 | 51 | # ColdStart tests are automated 52 | /client/test/coldstart/run-coldstart-*.json 53 | /client/test/coldstart/run-job-coldstart.json 54 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "Launch", 6 | "type": "node", 7 | "request": "launch", 8 | "program": "${workspaceRoot}/app.js", 9 | "stopOnEntry": false, 10 | "args": [], 11 | "cwd": "${workspaceRoot}", 12 | "preLaunchTask": null, 13 | "runtimeExecutable": null, 14 | "runtimeArgs": [ 15 | "--nolazy" 16 | ], 17 | "env": { 18 | "NODE_ENV": "development" 19 | }, 20 | "externalConsole": false, 21 | "sourceMaps": false, 22 | "outDir": null 23 | }, 24 | { 25 | "name": "Attach", 26 | "type": "node", 27 | "request": "attach", 28 | "port": 5858, 29 | "address": "localhost", 30 | "restart": false, 31 | "sourceMaps": false, 32 | "outDir": null, 33 | "localRoot": "${workspaceRoot}", 34 | "remoteRoot": null 35 | }, 36 | { 37 | "name": "Launch-Client", 38 | "type": "node", 39 | "request": "launch", 40 | "program": "${workspaceRoot}/client/cmdport.js", 41 | "stopOnEntry": false, 42 | "args": [], 43 | "cwd": "${workspaceRoot}/client", 44 | "preLaunchTask": null, 45 | "runtimeExecutable": null, 46 | "runtimeArgs": [ 47 | "--nolazy" 48 | ], 49 | "env": { 50 | "NODE_ENV": "development" 51 | }, 52 | "externalConsole": false, 53 | "sourceMaps": false, 54 | "outDir": null 55 | }, 56 | { 57 | "name": "Launch-localhost-e2e", 58 | "type": "node", 59 | "request": "launch", 60 | "program": "${workspaceRoot}/client/test/test-localhost.js", 61 | "stopOnEntry": false, 62 | "args": [], 63 | "cwd": "${workspaceRoot}/client", 64 | "preLaunchTask": null, 65 | "runtimeExecutable": null, 66 | "runtimeArgs": [ 67 | "--nolazy" 68 | ], 69 | "env": { 70 | "NODE_ENV": "development" 71 | }, 72 | "externalConsole": false, 73 | "sourceMaps": false, 74 | "outDir": null 75 | }, 76 | { 77 | "name": "HelloWorldMvc-Test-Controller", 78 | "type": "node", 79 | "request": "launch", 80 | "program": "${workspaceRoot}/client/controller.js", 81 | "stopOnEntry": false, 82 | "args": [ "--job", "test/test-job-helloworldmvc.json", "--topic", "cgrp/aspnet","--verbose"], 83 | "cwd": "${workspaceRoot}/client", 84 | "preLaunchTask": null, 85 | "runtimeExecutable": null, 86 | "runtimeArgs": [ 87 | "--nolazy" 88 | ], 89 | "env": { 90 | "NODE_ENV": "development" 91 | }, 92 | "externalConsole": false, 93 | "sourceMaps": false, 94 | "outDir": null 95 | }, 96 | { 97 | "name": "HelloWorldMvc-Test-ColdStart", 98 | "type": "node", 99 | "request": "launch", 100 | "program": "${workspaceRoot}/client/controller.js", 101 | "stopOnEntry": false, 102 | "args": [ "--job", "test/coldstart/run-job-coldstart.json", "--topic", "controller/peterhsu-pc2", "--verbose"], 103 | "cwd": "${workspaceRoot}/client", 104 | "preLaunchTask": null, 105 | "runtimeExecutable": null, 106 | "runtimeArgs": [ 107 | "--nolazy" 108 | ], 109 | "env": { 110 | "NODE_ENV": "development" 111 | }, 112 | "externalConsole": false, 113 | "sourceMaps": false, 114 | "outDir": null 115 | }, 116 | { 117 | "name": "MusicStore-Test-Controller", 118 | "type": "node", 119 | "request": "launch", 120 | "program": "${workspaceRoot}/client/controller.js", 121 | "stopOnEntry": false, 122 | "args": [ "--job", "test/run-job-musicstore.json", "--topic", "cgrp/aspnet","--verbose"], 123 | "cwd": "${workspaceRoot}/client", 124 | "preLaunchTask": null, 125 | "runtimeExecutable": null, 126 | "runtimeArgs": [ 127 | "--nolazy" 128 | ], 129 | "env": { 130 | "NODE_ENV": "development" 131 | }, 132 | "externalConsole": false, 133 | "sourceMaps": false, 134 | "outDir": null 135 | }, 136 | { 137 | "name": "Launch-Spawner", 138 | "type": "node", 139 | "request": "launch", 140 | "program": "${workspaceRoot}/client/test/test-spawner.js", 141 | "stopOnEntry": false, 142 | "args": [], 143 | "cwd": "${workspaceRoot}/client", 144 | "preLaunchTask": null, 145 | "runtimeExecutable": null, 146 | "runtimeArgs": [ 147 | "--nolazy" 148 | ], 149 | "env": { 150 | "NODE_ENV": "development" 151 | }, 152 | "externalConsole": false, 153 | "sourceMaps": false, 154 | "outDir": null 155 | }, 156 | { 157 | "name": "Launch-Clean", 158 | "type": "node", 159 | "request": "launch", 160 | "program": "${workspaceRoot}/client/cmdport.js", 161 | "stopOnEntry": false, 162 | "args": ["clean"], 163 | "cwd": "${workspaceRoot}/client", 164 | "preLaunchTask": null, 165 | "runtimeExecutable": null, 166 | "runtimeArgs": [ 167 | "--nolazy" 168 | ], 169 | "env": { 170 | "NODE_ENV": "development" 171 | }, 172 | "externalConsole": false, 173 | "sourceMaps": false, 174 | "outDir": null 175 | } 176 | 177 | ] 178 | } -------------------------------------------------------------------------------- /CODE-OF-CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Code of Conduct 2 | 3 | This project has adopted the code of conduct defined by the Contributor Covenant 4 | to clarify expected behavior in our community. 5 | 6 | For more information, see the [.NET Foundation Code of Conduct](https://dotnetfoundation.org/code-of-conduct). 7 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | Contributing 2 | ====== 3 | 4 | Information on contributing to this repo is in the [Contributing Guide](https://github.com/aspnet/Home/blob/dev/CONTRIBUTING.md) in the Home repo. 5 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) .NET Foundation. All rights reserved. 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); you may not use 4 | these files except in compliance with the License. You may obtain a copy of the 5 | License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software distributed 10 | under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 11 | CONDITIONS OF ANY KIND, either express or implied. See the License for the 12 | specific language governing permissions and limitations under the License. 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Overview 2 | 3 | Wave provides a tool-set for cross platform remote command execution. 4 | 5 | * Based on nodejs for platform abstraction 6 | * Uses MQTT broker for communication. 7 | * Mark down based execution engine 8 | * Controller/CLI for sending commands and viewing real time outputs from targets. 9 | 10 | ## Controller & Command Execution 11 | 12 | The controller is an orchestrator that coordinates and sequences commands. The controller subscribes on `job/controllerid` topic and uses commands from a markdown table as follows. The controller is based on callbacks from the target agent as a mechanism of flow control. Commands are defined in markdown table with the following layout - 13 | 14 | | Command | Host |Description| 15 | |-------------|-----------|-----------| 16 | | `./startServer.sh` | $(server) |server command| 17 | | `.\loadtest.cmd $(server) $(serverurl)` | $(client) | Client command | 18 | 19 | * [`Environment variables`](/../../issues/9) may be persisted across commands in the agent which will be available in a spawned process. 20 | * `logdir` and `cwd` can be changed as a part of the [environment command](/client#setting-environment-variables). 21 | 22 | ## Client CLI/Web Client 23 | 24 | The commands can be directly sent the target using the CLI. There is also a web client which gives a remote shell like experience. 25 | For details of the command line client goto [`./client`](/client) 26 | 27 | ## Agent & Controller Overview 28 | 29 | The agent communication is handled through a set of topics which it listens to and outputs messages to. The controller or CLI communicates to the well-know topic as the contracts defined below. 30 | 31 | |Topic|Description| 32 | |----|-----------| 33 | |`hostid`| Subscribed by the host for synchronous command execution | 34 | |`hostid/async`| Subscribed by the host and commands are executed asynchronously | 35 | |`hostid/output`| Command output stream | 36 | |`hostid/status` | Last command executed | 37 | |`client/hostid/config` | Birth and will message which is retained and includes config details like IP and status | 38 | 39 | * Hostid defaults to `hostname` unless we override the value. 40 | 41 | The diagram below provides an overview of the communication mechanism between the agents and CLI/Controller. 42 | 43 | ![Alt text](https://aspnet.github.io/Wave/images/waveflow.png) 44 | 45 | ## Agent Setup 46 | 47 | #### Windows 48 | 49 | Install Node if needed. 50 | ``` 51 | msiexec /i https://nodejs.org/dist/v5.11.0/node-v5.11.0-x64.msi /passive 52 | ``` 53 | Setup the agent with the required credentials. 54 | ```ps 55 | @powershell -NoProfile -ExecutionPolicy unrestricted -Command "&{$target='c:\cmdport\';$broker='test';$username='test';$password='test';iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/aspnet/Wave/master/scripts/Install.ps1'))}" 56 | ``` 57 | 58 | #### Ubuntu 14.04 59 | 60 | Git clone the repo and run the install script as follows. 61 | ``` 62 | git clone http://github.com/aspnet/Wave 63 | sudo ./Wave/scripts/install.sh testbroker testuser testpassword 64 | ``` 65 | 66 | This sets up the flow and connects to the broker and makes the machine ready for remote commands. Refer to [`install.sh`](/scripts/install.sh) for details. 67 | The following commands should be used to setup or remove the agent as needed. 68 | ``` 69 | sudo update-rc.d cmdport defaults 70 | sudo update-rc.d -f cmdport remove 71 | ``` 72 | 73 | Optional: 74 | 75 | To allow shutdown scenario to work, add the following line to the `sudoer` file to avoid interactive prompt. Note that we do not currently support `poweroff` and `reboot` commands. 76 | ``` 77 | user_name ALL=(ALL) NOPASSWD: /sbin/shutdown 78 | ``` 79 | 80 | #### Docker 81 | 82 | [![Docker Pulls](https://img.shields.io/docker/pulls/dotnetperf/wave.svg?maxAge=2592000?style=plastic)](https://hub.docker.com/r/dotnetperf/wave/) 83 | 84 | The docker image is based of the `node:argon` image and contains the agent that can be started using the following commands and the logs command enables you to see if the broker succcessfully connected or not. 85 | 86 | ``` 87 | docker pull dotnetperf/wave 88 | docker run --name wave1 -p 8001:8000 -e BROKER=[broker] -e PORT=[port] -e USERNAME=[username] -e PASSWORD=[password] -h [hostname] -d dotnetperf/wave 89 | docker logs wave1 90 | ``` 91 | The image takes 3 environment variables and the hostname which is used to setup the credentials in the agent. 92 | Refer to the [Dockerfile](scripts/Dockerfile) 93 | 94 | To connect to the running instance use the following command to start an interactive shell. 95 | 96 | ``` 97 | docker exec -it wave1 /bin/sh 98 | ``` 99 | 100 | Once you are done with the agent, you can use the following commands to stop the container and delete the image if necessary. 101 | 102 | ``` 103 | docker stop wave1 && docker rm wave1 104 | docker rmi dotnetperf/wave 105 | ``` 106 | 107 | If you are deploying containers on Ubuntu VM on Hyper-V, then: 108 | - run `nm-tool` on the Ubuntu VM and pick any one of the DNS addresses in the output 109 | - `sudo vi /etc/default/docker` and add the DNS ip to DOCKER_OPTS parameter. 110 | - `sudo service docker restart` 111 | 112 | ## Broker Setup 113 | 114 | The following instructions are for a standard [`Mosquitto`](http://mosquitto.org/) MQTT broker. 115 | 116 | 1. Use the following instructions to setup an MQTT broker for command dispatch. 117 | 118 | ``` 119 | sudo apt-add-repository ppa:mosquitto-dev/mosquitto-ppa 120 | sudo apt-get update 121 | sudo apt-get -y install mosquitto 122 | sudo apt-get -y install mosquitto-clients 123 | ``` 124 | 2. Configure the password and disable anonymous access 125 | 126 | ``` 127 | sudo mosquitto_passwd -c /etc/mosquitto/pwfile 128 | sudo nano /etc/mosquitto/mosquitto.conf 129 | ``` 130 | 131 | 3. Add the following to disable anonymous access and enable websockets. 132 | 133 | ``` 134 | password_file /etc/mosquitto/pwfile 135 | allow_anonymous false 136 | 137 | port 1883 138 | listener 1884 139 | protocol websockets 140 | ``` 141 | 142 | 4. Restart the service 143 | 144 | ``` 145 | sudo service mosquitto restart 146 | ``` 147 | 148 | 5. To be able to connect to the broker you need to open up the port 1883 for Mosquitto. The following setup is for an azure VM. Follow the bread crumb described below and allow incoming packets for port 1883 for Azure VMs. 149 | ``` 150 | VM >> Settings >> Network Interfaces >> Network Interface >> Settings >> Network Security Group >> Settings >> Inbound Rules 151 | ``` 152 | 153 | -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | const http = require('http'); 2 | const express = require("express"); 3 | const RED = require("node-red"); 4 | const path = require('path') 5 | const os = require('os') 6 | const config = require('./config'); 7 | const env = require('./libs/env') 8 | const log = require('./libs/log') 9 | const recover = require('./libs/recover') 10 | const ipUtil = require('./libs/ipUtil'); 11 | const objUtil = require('./libs/objUtil'); 12 | const setupNode = require('./libs/setupNode'); 13 | const setenvNode = require('./libs/setenvNode'); 14 | 15 | if (!config.broker || !config.broker.host) { 16 | return; 17 | } 18 | 19 | // Create an Express app 20 | var app = express(); 21 | 22 | // Add a simple route for static content served from 'public' 23 | app.use("/", express.static("public")); 24 | 25 | // Create a server 26 | var server = http.createServer(app); 27 | 28 | // Create the settings object - see default settings.js file for other options 29 | var hostname = os.hostname().toLowerCase(); 30 | 31 | var basedir = path.resolve('./flows/'); 32 | env.setFilename(path.join(basedir, "_envVars.json")); 33 | 34 | var localdir = path.join(basedir, 'logs/'); 35 | log.init(localdir); 36 | log.setFilename(path.join(basedir, "_logdir.json")); 37 | 38 | var clientconfig = { 39 | "clientid" : config.clientid, 40 | "hostname": hostname, 41 | "arch": os.arch(), 42 | "ostype": os.type(), 43 | "os": os.platform(), 44 | "ips": ipUtil.getIPs(), 45 | "timestamp": new Date(), 46 | "version" : config.version 47 | }; 48 | 49 | var settings = { 50 | httpAdminRoot: "/red", 51 | httpNodeRoot: "/api", 52 | userDir: basedir, 53 | functionGlobalContext: { 54 | log: log, 55 | env: env, 56 | setup : setupNode.setup, 57 | setenv : setenvNode, 58 | recover : recover 59 | }, 60 | verbose: false, 61 | flowFile: path.join(basedir, 'flows_Dispatcher.json'), 62 | mqtt_dynamic: 63 | { 64 | broker: config.broker.host, 65 | broker_port: (config.broker.port ? config.broker.port : 1883), 66 | broker_username: config.broker.username, 67 | broker_password: config.broker.password, 68 | clientid: config.clientid, 69 | clientconfig: objUtil.extend(clientconfig, { "status": "online" }), 70 | clientconfig_offline: objUtil.extend(clientconfig, { "status": "offline" }) 71 | } 72 | }; 73 | 74 | // Initialise the runtime with a server and settings 75 | RED.init(server, settings); 76 | 77 | // Serve the editor UI from /red 78 | app.use(settings.httpAdminRoot, RED.httpAdmin); 79 | 80 | // Serve the http nodes UI from /api 81 | app.use(settings.httpNodeRoot, RED.httpNode); 82 | 83 | server.listen(8000); 84 | 85 | // Start the runtime 86 | RED.start(); 87 | 88 | console.log("================================================="); 89 | console.log("Flows Dir : " + path.resolve(settings.userDir)); 90 | console.log("Node-Red Url : http://localhost:8000/red/"); 91 | console.log("================================================="); 92 | -------------------------------------------------------------------------------- /broker/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:14.04 2 | ENV DEBIAN_FRONTEND noninteractive 3 | COPY install-broker.sh . 4 | RUN ./install-broker.sh 5 | COPY setpwd.sh . 6 | EXPOSE 1883 7 | EXPOSE 1884 8 | RUN service mosquitto stop 9 | COPY mosquitto.conf . 10 | COPY aclfile.conf . 11 | RUN touch /etc/mosquitto/pwfile 12 | CMD ./setpwd.sh ${ADMINPWD} ${READPWD} && mosquitto -c mosquitto.conf 13 | -------------------------------------------------------------------------------- /broker/aclfile.conf: -------------------------------------------------------------------------------- 1 | user readuser 2 | topic read # 3 | 4 | user admin 5 | topic readwrite # 6 | -------------------------------------------------------------------------------- /broker/docker-build.sh: -------------------------------------------------------------------------------- 1 | pushd `dirname $0` > /dev/null 2 | docker build -t dotnetperf/broker . 3 | -------------------------------------------------------------------------------- /broker/docker-run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ "$#" -ne 3 ]; then 4 | echo "Usage: ./docker-run.sh {hostname} {admin-password} {readclient-password}" 5 | exit 1 6 | fi 7 | 8 | _HOSTNAME=$1 9 | _ADMINPWD=$2 10 | _READPWD=$3 11 | docker stop $_HOSTNAME 12 | docker rm $_HOSTNAME 13 | _CONTAINERID=$(docker run --name $_HOSTNAME -p 1883-1884:1883-1884/tcp -e ADMINPWD=$_ADMINPWD -e READPWD=$_READPWD -h $_HOSTNAME -d dotnetperf/broker) 14 | sleep 2 15 | docker logs $_CONTAINERID 16 | -------------------------------------------------------------------------------- /broker/install-broker.sh: -------------------------------------------------------------------------------- 1 | apt-get update 2 | apt-get upgrade -y 3 | apt-get -y install software-properties-common 4 | apt-add-repository -y ppa:mosquitto-dev/mosquitto-ppa 5 | apt-get update 6 | apt-get -y install mosquitto 7 | apt-get -y install mosquitto-clients 8 | -------------------------------------------------------------------------------- /broker/mosquitto.conf: -------------------------------------------------------------------------------- 1 | password_file /etc/mosquitto/pwfile 2 | allow_anonymous false 3 | acl_file /aclfile.conf 4 | 5 | listener 1883 6 | protocol mqtt 7 | 8 | listener 1884 9 | protocol websockets 10 | -------------------------------------------------------------------------------- /broker/setpwd.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | mosquitto_passwd -b /etc/mosquitto/pwfile admin $1 3 | mosquitto_passwd -b /etc/mosquitto/pwfile readuser $2 4 | 5 | -------------------------------------------------------------------------------- /brokeragent/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:14.04 2 | ENV DEBIAN_FRONTEND noninteractive 3 | 4 | # Install prereqs 5 | COPY install-prereqs.sh . 6 | RUN ./install-prereqs.sh 7 | 8 | # Broker setup 9 | COPY setpwd.sh . 10 | EXPOSE 1883 11 | EXPOSE 1884 12 | COPY mosquitto.conf /etc/mosquitto/mosquitto.conf 13 | COPY aclfile.conf /etc/mosquitto/aclfile.conf 14 | RUN touch /etc/mosquitto/pwfile 15 | 16 | # Agent Setup 17 | RUN git clone http://github.com/aspnet/wave 18 | WORKDIR /wave 19 | RUN npm install 20 | RUN ./scripts/write_version.sh 21 | EXPOSE 8000 22 | CMD /setpwd.sh ${ADMINPWD} ${READPWD} && service mosquitto restart && /usr/bin/node setup.js -h ${BROKER} -p {PORT} -u ${USERNAME} -P ${PASSWORD} && /usr/bin/node app.js 23 | 24 | -------------------------------------------------------------------------------- /brokeragent/aclfile.conf: -------------------------------------------------------------------------------- 1 | user readuser 2 | topic read # 3 | 4 | user admin 5 | topic readwrite # 6 | -------------------------------------------------------------------------------- /brokeragent/docker-build.sh: -------------------------------------------------------------------------------- 1 | pushd `dirname $0` > /dev/null 2 | docker build -t dotnetperf/brokeragent . 3 | -------------------------------------------------------------------------------- /brokeragent/docker-run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ "$#" -ne 7 ]; then 4 | echo "Usage: ./docker-run.sh {hostname} {admin-password} {readuser-password} {broker} {port} {username} {password}" 5 | exit 1 6 | fi 7 | 8 | _HOSTNAME=$1 9 | _ADMINPWD=$2 10 | _READPWD=$3 11 | _BROKER=$4 12 | _PORT=$5 13 | _USERNAME=$6 14 | _PASSWORD=$7 15 | docker stop $_HOSTNAME 16 | docker rm $_HOSTNAME 17 | _CONTAINERID=$(docker run --name $_HOSTNAME -p 1883-1884:1883-1884/tcp -e ADMINPWD=$_ADMINPWD -e READPWD=$_READPWD -e BROKER=$_BROKER -e PORT=$_PORT -e USERNAME=$_USERNAME -e PASSWORD=$_PASSWORD -h $_HOSTNAME -d dotnetperf/brokeragent) 18 | sleep 3 19 | docker logs $_CONTAINERID 20 | -------------------------------------------------------------------------------- /brokeragent/install-prereqs.sh: -------------------------------------------------------------------------------- 1 | apt-get update 2 | apt-get upgrade -y 3 | apt-get -y install software-properties-common 4 | apt-add-repository -y ppa:mosquitto-dev/mosquitto-ppa 5 | apt-get update 6 | apt-get -y install mosquitto 7 | apt-get -y install mosquitto-clients 8 | apt-get -y install git 9 | curl -sL https://deb.nodesource.com/setup | sudo - 10 | apt-get -y install nodejs 11 | apt-get -y install npm 12 | ln -s /usr/local/lib/node /usr/lib/node 13 | ln -s /usr/local/bin/node-waf /usr/bin/node-waf 14 | ln -s "$(which nodejs)" /usr/bin/node 15 | -------------------------------------------------------------------------------- /brokeragent/mosquitto.conf: -------------------------------------------------------------------------------- 1 | password_file /etc/mosquitto/pwfile 2 | allow_anonymous false 3 | acl_file /etc/mosquitto/aclfile.conf 4 | 5 | listener 1883 6 | protocol mqtt 7 | 8 | listener 1884 9 | protocol websockets 10 | -------------------------------------------------------------------------------- /brokeragent/setpwd.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | mosquitto_passwd -b /etc/mosquitto/pwfile admin $1 3 | mosquitto_passwd -b /etc/mosquitto/pwfile readuser $2 4 | 5 | -------------------------------------------------------------------------------- /client/README.md: -------------------------------------------------------------------------------- 1 | #Wave Client CLI 2 | 3 | The client is a wrapper on top of the node MQTT implemenation. The main change is that it reads credentials from a file called `_creds.json`. 4 | The credentials file can be generated using setup.js as follows. **Make sure you npm install the modules needed.** 5 | 6 | ``` 7 | npm install 8 | ../setup.js -h broker -u username -P password 9 | ``` 10 | 11 | # Command Execution 12 | 13 | ## Basic Commands. 14 | 15 | |Commands | Example | Description | 16 | |------------|---------|-------------| 17 | |Execute Sync|`./cmdport.js send -t "target_host" -m "command to execute"` | You can run the following command to execute a command remotely and they will be queued one after another on the target.| 18 | |Execute Async|`./cmdport.js send -t "target_host/async" -m "non-blocking command"` |When we want to have a non-blocking command like killing a process or reboot if the machine goes into a bad state, we can use the async topic to push the command through to the machine. | 19 | |Output/Status|`./cmdport.js subscribe -t "target_host/+"`| Command execution publishes the output to the `host/output` topic and can be subscribed by the client. The status of the last command is published as a retained message on `host/status`. | 20 | 21 | 22 | ## Commands with options. 23 | 24 | Commands can be directly executed or options can be passed in using json as follow. 25 | 26 | ``` 27 | { 28 | command : "/home/aspuser/.dotnet/dotnet run" 29 | cwd : "/home/aspuser/app/" 30 | logfile : "dotnet_out.txt", 31 | continueOnError : "true" 32 | } 33 | ``` 34 | |Option| Description | 35 | |------|-------------| 36 | | command | The executable that needs to be launced on the target machine. | 37 | | cwd | The current workign directory that should be used when spawning the process.| 38 | | logfile | The file name used to output the stdout & stderr. The pid of the process is inserted into filename. So if the passed in filenamem is `output.txt` the output would be placed in `output_{pid}.txt` | 39 | | env | Set of key value pairs which will be set before launch of the process. | 40 | | continueOnError | The job would typical fail if a command fails. However, if continueOnError is set to "true", the error would be ignored | 41 | 42 | ### Setting Environment Variables. 43 | 44 | Environment variables can be set in 2 ways 45 | 1. Persisted variables using a `setenv` command 46 | 2. Per command environment varialbles as a `env` option. 47 | 48 | When launching a process are composed using the following precedence 49 | 50 | ``` 51 | parent process variables << presistent variables << command environment vars 52 | ``` 53 | 54 | The following setenv command can be used to persist variables and configure the `logdir` and a base `CWD` as follows. 55 | 56 | ``` 57 | { 58 | "command": "setenv", 59 | "options" " { 60 | "env": { 61 | "Test": "TestValue" 62 | }, 63 | "logdir": "X:/logoutputpath", 64 | "cwd" : "/mnt/logs" 65 | } 66 | } 67 | ``` 68 | 69 | |Variable|Description| 70 | |--------|-----------| 71 | |env| Key value pairs which will be setup before a command process is launched.| 72 | |logdir | The directory used to save the output log files. If the directory doesnt exist then it is created. If there is a failure in creation of the directory, the command fails. | 73 | 74 | # Management operations 75 | 76 | | Command | Description | 77 | |---------|-------------| 78 | | `./cmdport.js subscribe -t "client/+"` | Client notifications - To get notification and birth messages for clients use the `client/+` topic. | 79 | | `./cmdport.js publish -t "client/[hostname]/config" -p -n -r` | Remove host - Sends a null message to delete the config info | 80 | | `./cmdport.js subscribe -t '#' -v` | Subscribe to Universe - Used for debugging the broker and viewing all message. | 81 | 82 | 83 | ### Debugging the broker 84 | 85 | As a test you can subscribe to all topics as well using the cmdport client as it is just a simple wrapper over mqtt. 86 | 87 | ``` 88 | ./cmdport.js subscribe -t '#' -v 89 | host-nix/config {"hostname":"host-nix","arch":"x64","ostype":"Linux","os":"linux","ips":["10.21.0.5"]} 90 | client/host-win/config {"hostname":"host-host","arch":"x64","ostype":"Windows_NT","os":"win32","ips":["10.30.169.104"]} 91 | client/host-Linux1/config {"hostname":"host-Linux1","arch":"x64","ostype":"Linux","os":"linux","ips":["10.2.0.1"]} 92 | ``` 93 | -------------------------------------------------------------------------------- /client/args-util.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var mqtt = require('mqtt'), 4 | path = require('path'), 5 | fs = require('fs'), 6 | concat = require('concat-stream'), 7 | helpMe = require('help-me')({ 8 | dir: path.join(__dirname, '..', 'doc') 9 | }), 10 | minimist = require('minimist'); 11 | 12 | function add_creds(args) { 13 | try { 14 | // a path we KNOW might not exists since it might not be configured. 15 | var credentials = require('./_creds') 16 | } catch (e) { 17 | console.log('Setup credentials using setup.js'); 18 | return; 19 | } 20 | 21 | var broker = credentials.broker; 22 | var options = [ 23 | "--hostname", broker.host, 24 | "--port", broker.port, 25 | "--username", broker.username, 26 | "--password", broker.password, 27 | "-q", "1" 28 | ]; 29 | 30 | for (var i = 0; i <= args.length; i++) { 31 | if (args[i] == '-t') { 32 | args[i + 1] = args[i + 1].toLowerCase(); 33 | } 34 | } 35 | args = args.concat(options); 36 | return args; 37 | } 38 | 39 | function add_additional_args(args) { 40 | args = minimist(args, { 41 | string: ['hostname', 'username', 'password', 'key', 'cert', 'ca'], 42 | integer: ['port', 'qos', 'keepAlive'], 43 | boolean: ['stdin', 'help', 'clean', 'insecure'], 44 | alias: { 45 | port: 'p', 46 | hostname: ['h', 'host'], 47 | topic: 't', 48 | qos: 'q', 49 | clean: 'c', 50 | keepalive: 'k', 51 | clientId: ['i', 'id'], 52 | username: 'u', 53 | password: 'P', 54 | protocol: ['C', 'l'], 55 | verbose: 'v', 56 | help: '-H', 57 | ca: 'cafile' 58 | }, 59 | default: { 60 | host: 'localhost', 61 | qos: 0, 62 | retain: false, 63 | clean: true, 64 | keepAlive: 30 // 30 sec 65 | } 66 | }); 67 | 68 | args.topic = args.topic || args._.shift(); 69 | 70 | if (args.key) { 71 | args.key = fs.readFileSync(args.key); 72 | } 73 | 74 | if (args.cert) { 75 | args.cert = fs.readFileSync(args.cert); 76 | } 77 | 78 | if (args.ca) { 79 | args.ca = fs.readFileSync(args.ca); 80 | } 81 | 82 | if (args.key && args.cert && !args.protocol) { 83 | args.protocol = 'mqtts'; 84 | } 85 | 86 | if (args.insecure) { 87 | args.rejectUnauthorized = false; 88 | } 89 | 90 | if (args.port) { 91 | if (typeof args.port !== 'number') { 92 | console.warn('# Port: number expected, \'%s\' was given.', typeof args.port); 93 | return args; 94 | } 95 | } 96 | 97 | if (args['will-topic']) { 98 | args.will = {}; 99 | args.will.topic = args['will-topic']; 100 | args.will.payload = args['will-message']; 101 | args.will.qos = args['will-qos']; 102 | args.will.retain = args['will-retain']; 103 | } 104 | 105 | args.keepAlive = args['keep-alive']; 106 | 107 | return args; 108 | } 109 | 110 | 111 | module.exports.process = function(args) { 112 | args = add_creds(args); 113 | args = add_additional_args(args); 114 | return args; 115 | } -------------------------------------------------------------------------------- /client/clean.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 'use strict'; 3 | 4 | var mqtt = require('mqtt') 5 | , colors = require('colors') 6 | , path = require('path') 7 | , fs = require('fs') 8 | , concat = require('concat-stream') 9 | , helpMe = require('help-me')({ 10 | dir: path.join(__dirname, '..', 'doc') 11 | }) 12 | , minimist = require('minimist'); 13 | 14 | function drain(args) { 15 | args = minimist(args, { 16 | boolean: ['delete-offline'], 17 | alias: { 18 | topic: 't', 19 | qos: 'q' 20 | }, 21 | default: { 22 | topic: 'client/#', 23 | qos: 0 24 | } 25 | }) 26 | 27 | if(!args['delete-offline']){ 28 | console.log(colors.yellow("Specify --delete-offline to delete the stale configs")); 29 | } 30 | 31 | var client = mqtt.connect(args); 32 | 33 | client.on('connect', function () { 34 | client.subscribe(args.topic, { qos: args.qos }, function (err, result) { 35 | result.forEach(function (sub) { 36 | if (sub.qos > 2) { 37 | console.error('subscription negated to', sub.topic, 'with code', sub.qos); 38 | process.exit(1); 39 | } 40 | }) 41 | }); 42 | }); 43 | 44 | // Wait for 3 seconds 45 | var waitTime = 3000; 46 | var lastMsg = { 47 | timestamp: new Date() 48 | }; 49 | 50 | setInterval(function () { 51 | var current = new Date(); 52 | var delta = current - lastMsg.timestamp; 53 | console.log("Wating... - [%s] ", delta/1000); 54 | if (delta < waitTime) { 55 | return true; 56 | } 57 | 58 | console.log("No messages during the last 3 seconds and so stopping clean. You can try running clean again to wait for more.") 59 | client.end(); 60 | process.exit(0); 61 | }, waitTime); 62 | 63 | var deleted = {}; 64 | client.on('message', function (topic, payload) { 65 | lastMsg.timestamp = new Date(); 66 | TryDelete(client, topic, payload, args, deleted) 67 | }); 68 | } 69 | 70 | 71 | function TryDelete(client, topic, payload, args, deleted) { 72 | var shouldDelete = true; 73 | var days = -1; 74 | var offline = true; 75 | try { 76 | payload = JSON.parse(payload); 77 | if (payload.timestamp) { 78 | var sourceDateTime = new Date(payload.timestamp); 79 | var current = new Date(); 80 | days = Math.round((current - sourceDateTime) / (1000 * 60 * 60 * 24)); 81 | offline = payload.status != 'online'; 82 | } 83 | } catch (e) { 84 | } 85 | 86 | if ((days == -1) || offline) { 87 | days = colors.red(days) 88 | } 89 | else { 90 | days = colors.green("online " + days); 91 | topic = colors.green(topic); 92 | shouldDelete = false; 93 | } 94 | 95 | console.log("%s \t%s days %s", topic, days, shouldDelete ? "--will-delete" : 'ONLINE'); 96 | if (args['delete-offline'] && shouldDelete) { 97 | if (!deleted[topic]) { 98 | deleted[topic] = payload; 99 | args.retain = true; 100 | client.publish(topic, '', args); 101 | } 102 | } 103 | } 104 | 105 | module.exports = drain; 106 | 107 | if (require.main === module) { 108 | drain(process.argv.slice(2)) 109 | } -------------------------------------------------------------------------------- /client/cmdport.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 'use strict'; 3 | 4 | try { 5 | // a path we KNOW might not exists since it might not be configured. 6 | var credentials = require('./_creds') 7 | } 8 | catch (e) { 9 | console.log('Setup credentials using setup.js'); 10 | return; 11 | } 12 | 13 | var broker = credentials.broker; 14 | 15 | function cli(inputargs) { 16 | 17 | var commist = require('commist')() 18 | , helpMe = require('help-me')({ dir: './node_modules/mqtt/doc' }); 19 | 20 | commist.register('publish', require('./node_modules/mqtt/bin/pub')); 21 | commist.register('send', require('./node_modules/mqtt/bin/pub')); 22 | commist.register('subscribe', require('./node_modules/mqtt/bin/sub')); 23 | commist.register('clean', require('./clean')); 24 | commist.register('start', start); 25 | 26 | commist.register('version', function() { 27 | console.log('MQTT.js version:', require('./node_modules/mqtt/package.json').version); 28 | }); 29 | 30 | commist.register('help', helpMe.toStdout); 31 | var options = 32 | [ 33 | "--hostname", broker.host, 34 | "--port", broker.port, 35 | "--username", broker.username, 36 | "--password", broker.password, 37 | "-q", "1" 38 | ]; 39 | 40 | var args = inputargs.concat(options); 41 | 42 | // Canonicalize the hostname. 43 | for (var i = 0; i <= args.length; i++) { 44 | if (args[i] == '-t') { 45 | args[i + 1] = args[i + 1].toLowerCase(); 46 | } 47 | } 48 | 49 | if (null !== commist.parse(args)) { 50 | console.log('No such command:', '\n'); 51 | helpMe.toStdout(); 52 | } 53 | } 54 | 55 | if (require.main === module) { 56 | cli(process.argv.slice(2)); 57 | } 58 | 59 | function send(topic, payload) { 60 | if (typeof (payload) != 'string') { 61 | payload = JSON.stringify(payload); 62 | } 63 | var args = ["send", "-t", topic, "-m", payload]; 64 | cli(args); 65 | } 66 | 67 | function start(args) { 68 | var minimist = require('minimist'); 69 | var fs = require('fs'); 70 | var myargs = minimist(process.argv.slice(2), { 71 | string: ['spec', 'env','job'] 72 | }); 73 | 74 | function startTest(test){ 75 | var msg = { 76 | spec: test.spec, 77 | env: JSON.parse(fs.readFileSync(test.env)) 78 | }; 79 | var args = ["send", "-t", myargs.topic, "-m", JSON.stringify(msg)]; 80 | cli(args); 81 | } 82 | 83 | if(myargs.job) { 84 | var jobspec = JSON.parse(fs.readFileSync(myargs.job)); 85 | for(var test in jobspec) { 86 | startTest(jobspec[test]); 87 | } 88 | }else if (myargs.env && myargs.spec) { 89 | startTest(myargs); 90 | } 91 | 92 | } 93 | 94 | module.exports.cli = cli; 95 | module.exports.send = send; 96 | module.exports.start = start; 97 | -------------------------------------------------------------------------------- /client/controller-unittest.js: -------------------------------------------------------------------------------- 1 | var os = require('os'); 2 | var colors = require('colors'); 3 | var sleep = require('sleep'); 4 | 5 | var nodejs = 'node'; 6 | 7 | var controllertopic = 'job/' + os.hostname() + '/test'; 8 | 9 | const spawn = require('child_process').spawn; 10 | 11 | const controller = spawn(nodejs, ['controller.js', '--test', '--verbose']); 12 | controller.on('close', function(code) { 13 | console.log('controller exited with code ' + code); 14 | if (code == 0) { 15 | console.log('Test Status: Success'); 16 | } 17 | agent.kill('SIGHUP'); 18 | }); 19 | controller.stdout.on('data', function(data) { 20 | process.stdout.write(colors.green('[Controller]: ') + data); 21 | }); 22 | controller.stderr.on('data', function(data) { 23 | process.stdout.write(colors.green('[Controller]: Error') + data); 24 | }); 25 | console.log(colors.green('[Controller] Started - Listening on '+ controllertopic)); 26 | 27 | var agenttopic = 'client/+/test'; 28 | const agent = spawn(nodejs, ['./cmdport.js', 'subscribe', '-t', agenttopic]); 29 | agent.stdout.on('data', function(data) { 30 | console.log(colors.blue('[Agent]: InMsg') + data); 31 | console.log(colors.yellow('[Test]: ') + "OutMsg to " + controllertopic + " " + data); 32 | var test = spawn(nodejs, ['./cmdport.js', 'send', '-t', controllertopic, '-m', data]); 33 | 34 | }); 35 | console.log(colors.blue('[Agent] Started - Listening on '+ agenttopic)); 36 | 37 | sleep.sleep(2); 38 | 39 | console.log(colors.yellow("[Test]: ") + " Kickoff test run - Sending message to " + controllertopic + ": { 'testspec': './test.md', 'env': {'server': 'M1', 'client': 'M2' }}"); 40 | const test = spawn(nodejs, ['./cmdport.js', 'send', '-t', controllertopic, '-m', "{ 'testspec': './test.md', 'env': {'server': 'M1', 'client': 'M2' }}"]); 41 | test.on('close', function(code) { 42 | console.log('testcmd exited with code ' + code); 43 | }); 44 | -------------------------------------------------------------------------------- /client/controller.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var exec = require('child_process').exec, 4 | colors = require('colors'), 5 | os = require('os'), 6 | util = require('util'), 7 | minimist = require('minimist'), 8 | mqtt = require('mqtt'), 9 | args_util = require('./args-util.js'), 10 | msgProcessor = require('./messageprocessor.js'), 11 | cmdport = require('./cmdport'), 12 | fs = require('fs'), 13 | utils = require('./libs/utils'), 14 | http = require('http'), 15 | Q = require('q'); 16 | 17 | function updateQosClientId(inputargs) { 18 | inputargs = inputargs.concat("--no-clean", "-i", os.hostname().toLowerCase() + "_controller") 19 | return inputargs; 20 | } 21 | 22 | function start(inputargs) { 23 | inputargs = updateQosClientId(inputargs); 24 | var args = args_util.process(inputargs); 25 | var myargs = minimist(inputargs, { 26 | string: ['--test', '--verbose', '--start', 'spec', 'env', 'job'] 27 | }); 28 | 29 | if (!args.topic) { 30 | args.topic = 'job/' + os.hostname(); 31 | } 32 | 33 | if (myargs.test) { 34 | args.topic = args.topic + '/test'; 35 | } 36 | 37 | args.topic = args.topic.toLowerCase(); 38 | 39 | var environment = {}; 40 | 41 | var client = mqtt.connect(args); 42 | client.on('error', function(error) { 43 | console.log(error); 44 | return; 45 | }); 46 | client.on('close', function(error) { 47 | console.log(error); 48 | return; 49 | }); 50 | client.on('connect', function () { 51 | client.subscribe(args.topic, { qos: args.qos }, function (err, result) { 52 | result.forEach(function (sub) { 53 | console.log("[Subscribe ] " + args.topic); 54 | if (sub.qos > 2) { 55 | console.error('subscription negated to', sub.topic, 'with code', sub.qos); 56 | process.exit(1); 57 | } 58 | }) 59 | }); 60 | }); 61 | var subscriptions = {}; 62 | subscriptions[args.topic] = { 63 | handler: function (topic, msg) { 64 | log('['+topic+'] ' + msg); 65 | var msg = JSON.parse(msg); 66 | 67 | //Execute dummy message 68 | if (myargs.test) { 69 | // We know that we need to execute this as it has an undefined exitcode. 70 | if (msg.command && msg.exitcode == undefined) { 71 | console.log("[Exec+Callback] " + msg.command); 72 | msg.exitcode = 0; 73 | // Send message back to controller. 74 | cmdport.send(args.topic, msg); 75 | return; 76 | } 77 | } 78 | 79 | var nextcmd = msgProcessor.process(msg, args.topic); 80 | if (nextcmd == null) { 81 | //client.end(); 82 | // setTimeout(function() { 83 | // process.exit(); 84 | // }, 1000); 85 | return; 86 | } 87 | var setenvPromise = Q.resolve(); 88 | if(nextcmd.setenv) { 89 | for (var target in nextcmd.env) { 90 | var newenv = nextcmd.env[target]; 91 | var envmsg = { 92 | command: "setenv", 93 | logdir: nextcmd.env[target]["logdir"], 94 | } 95 | if( typeof newenv["$path"] != 'undefined'){ 96 | envmsg.path = newenv["$path"]; 97 | delete newenv["$path"]; 98 | } 99 | envmsg.env = newenv; 100 | setenvPromise = sendCommand(nextcmd.env[target][target], envmsg); 101 | } 102 | } 103 | var clientTopic = nextcmd.target; 104 | if(nextcmd.msg.async) { 105 | clientTopic = clientTopic + "/async"; 106 | } 107 | 108 | //For the test we send it back to the controller. 109 | if (myargs.test) { 110 | clientTopic = args.topic; 111 | } 112 | 113 | setenvPromise.then (function() { 114 | sendCommand(clientTopic, nextcmd.msg); 115 | }).then(function(){ 116 | if(nextcmd.msg.async){ 117 | nextcmd.msg.continue = true; 118 | sendCommand(args.topic, nextcmd.msg); 119 | } 120 | }); 121 | } 122 | }; 123 | 124 | client.on('message', function (topic, msg) { 125 | var subscription = subscriptions[topic]; 126 | if (!subscription) { 127 | //Get client topic 128 | var clientTopic = getClientTopic(topic); 129 | subscription = subscriptions[clientTopic]; 130 | } 131 | 132 | if (subscription) { 133 | subscription.handler(topic, msg); 134 | } 135 | }); 136 | 137 | function sendCommand(topic, msg) { 138 | 139 | var promise; 140 | 141 | if (myargs.verbose) { 142 | promise = subscribeToTopic(topic + "/status") 143 | .then( function() { 144 | return subscribeToTopic(topic); 145 | }) 146 | .then( function() { 147 | return subscribeToTopic(topic+"/output"); 148 | }); 149 | } 150 | 151 | promise = promise.then ( function() { 152 | return cmdportSend(topic, msg); 153 | }); 154 | return promise; 155 | }; 156 | 157 | function cmdportSend(topic, msg){ 158 | var deferred = Q.defer(); 159 | var header = "Send"; 160 | if(msg.async) { 161 | header = header + " async"; 162 | } 163 | log('['+header+' ]' + topic + ': ' + JSON.stringify(msg)); 164 | cmdport.send(topic, msg); 165 | deferred.resolve(); 166 | return deferred.promise; 167 | } 168 | function subscribeToTopic(clientTopic) { 169 | var deferred = Q.defer(); 170 | if (!clientTopic) { 171 | deferred.resolve(); 172 | return deferred.promise; 173 | } 174 | if (subscriptions[clientTopic]) { 175 | deferred.resolve(); 176 | return deferred.promise; 177 | } 178 | 179 | subscriptions[clientTopic] = { 180 | handler: function (t, msg) { 181 | console.log(colors.yellow(util.format("[%s]%s: ", t, msg))); 182 | } 183 | } 184 | 185 | client.subscribe(clientTopic, { 186 | qos: 0 187 | }, function (err, result) { 188 | if (err) { 189 | console.log(colors.red("ERR: " + err)); 190 | return deferred.reject("Error in susbscribing to "+clientTopic); 191 | //process.exit(1); 192 | } 193 | console.log("[Subscribe ]" + clientTopic+":"); 194 | deferred.resolve(); 195 | }); 196 | return deferred.promise; 197 | } 198 | 199 | function getClientTopic(topic) { 200 | //Get client topic 201 | if (topic.indexOf('\\') > -1) { 202 | var match = topic.match(/(.*?)\//); 203 | if (match) { 204 | return match[1] + "/output"; 205 | } 206 | } 207 | 208 | return topic = topic + "/output"; 209 | } 210 | 211 | function log(msg) { 212 | if (myargs.verbose) { 213 | console.log(msg); 214 | } 215 | } 216 | 217 | function exec_cmd(cmd) { 218 | var child = exec(cmd, 219 | function (error, stdout, stderr) { 220 | if (stdout) { 221 | console.log('stdout: ' + stdout); 222 | } 223 | if (stderr) { 224 | console.log('stderr: ' + stderr); 225 | } 226 | if (error !== null) { 227 | console.log('exec error: ' + error); 228 | } 229 | }); 230 | } 231 | 232 | if(myargs.job || (myargs.env && myargs.spec) ) { 233 | cmdport.start(myargs); 234 | } 235 | } 236 | 237 | module.exports.start = start; 238 | 239 | if (require.main === module) { 240 | console.log(" Starting...") 241 | start(process.argv.slice(2)) 242 | } -------------------------------------------------------------------------------- /client/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 7 | 8 | 9 | 11 | 12 | 13 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 57 |
58 |
59 | 81 |
82 |
83 | 84 |
85 |

86 |
87 |
88 |
89 | 90 |
91 |
92 | 93 | Send 94 |
95 |
96 |
97 |
98 |
99 |
100 | 101 | 102 | -------------------------------------------------------------------------------- /client/libs/mdparser.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 'use strict'; 3 | 4 | var fs = require('fs'); 5 | var util = require('util'); 6 | var process = require('process'); 7 | 8 | function getcommand(filename, index, env) { 9 | var contents = getContents(filename); 10 | if(contents == null) { 11 | return null; 12 | } 13 | var cmd = getcommandText(contents, index); 14 | return applyEnv(cmd, env); 15 | } 16 | 17 | function getContents(filename){ 18 | var stat = fs.lstatSync(filename); 19 | if(stat.isFile()) { 20 | return fs.readFileSync(filename, 'utf8').replace(/\r\n/g, '\n'); 21 | } 22 | return null; 23 | //if(filename.startWith("http")) 24 | } 25 | 26 | function getcommandText(contents, index) { 27 | 28 | var re = /\|\s*Command\s*\|\s*Host.*\n(?:(?:\|[\S:-]+)+)\n/m; 29 | var commandRe = /\|\s*`(.*?)`\s*\|\s(\$\((.*?)\))\s*\|\s*/m; 30 | var arrMatches = contents.match(re); 31 | if (arrMatches) { 32 | var commands = contents.substr(arrMatches.index + arrMatches[0].length).split('\n'); 33 | if (commands && commands.length > index) { 34 | var command = commands[index]; 35 | if (command) { 36 | //var parts = command.match(commandRe); 37 | var parts = command.split("|"); 38 | if (parts) { 39 | // Command is of the format `SomeCommand` 40 | var cmd = parts[1].match(/`(.*?)`/m); 41 | 42 | // Target is of the format $(TARGET) 43 | var targetName = parts[2].match(/\$\((.*?)\)/m); 44 | 45 | var command = { 46 | command: cmd[1], 47 | target: parts[2].trim(), 48 | targetName: targetName[1] 49 | }; 50 | 51 | // cwd is of the format 52 | var cwd = parts[1].match(/cwd=\"(.*?)\"/m); 53 | if(cwd && cwd.length > 1) { 54 | command.cwd = cwd[1]; 55 | } 56 | command.async = false; 57 | var async = parts[1].match(/async=\"(.*?)\"/m); 58 | if(async && async.length >1) { 59 | command.async = (async[1] == "true") ? true:false; 60 | } 61 | var continueOnError = parts[1].match(/continueOnError=\"(.*?)\"/m); 62 | if(continueOnError && continueOnError.length > 1) { 63 | command.continueOnError = continueOnError[1].toLowerCase(); 64 | } 65 | return command; 66 | } 67 | } 68 | } 69 | } 70 | return ""; 71 | }; 72 | 73 | function applyEnv(cmd, env) { 74 | if (cmd) { 75 | for(var property in cmd) { 76 | cmd[property] = _replaceEnv(cmd[property], env[cmd.targetName]); 77 | } 78 | cmd.command = _replaceEnv(cmd.command, env[cmd.targetName]); 79 | 80 | if(/\.ps1/.test(cmd.command)) { 81 | if(!(/powershell.exe/.test(cmd.command))) { 82 | cmd.command = "powershell.exe -File " + cmd.command; 83 | } 84 | } 85 | 86 | var ps1 = cmd.command.match(/(\w+?)\.ps1/); 87 | if(ps1 && ps1.length > 1) { 88 | cmd.logfile = ps1[1] + ".log"; 89 | } 90 | else{ 91 | var exe = cmd.command.match(/cmd.exe \/c (\w+)/); 92 | if(exe && exe.length>1){ 93 | cmd.logfile=exe[1] + ".log"; 94 | } 95 | else { 96 | var executable = cmd.command.match(/^\s*(\w+?)\s/); 97 | if(executable && executable.length>1) { 98 | cmd.logfile = executable[1] + ".log"; 99 | } 100 | } 101 | } 102 | } 103 | return cmd; 104 | } 105 | 106 | function _replaceEnv(input, env) { 107 | if (typeof (input) == 'string') 108 | return input.replace(/\$\((.*?)\)/g, function(match, $1) { 109 | return env[$1] || match; 110 | }); 111 | 112 | return input; 113 | } 114 | 115 | function cli() { 116 | var args = process.argv.slice(2); 117 | var filename = args[0]; 118 | var index = args.length > 1 ? args[1] : 0; 119 | getcommand(filename, index); 120 | } 121 | 122 | if (require.main === module) { 123 | cli(); 124 | } 125 | 126 | module.exports.getcommand = getcommand; 127 | -------------------------------------------------------------------------------- /client/libs/spawner.js: -------------------------------------------------------------------------------- 1 | const EventEmitter = require('events'); 2 | const util = require('util'); 3 | var spawn = require('child_process').spawn; 4 | var isUtf8 = require('is-utf8'); 5 | 6 | var spawnerOptions = { 7 | verbose: false 8 | } 9 | 10 | function Spawner(options) { 11 | 12 | /* 13 | options 14 | { 15 | command : "filepath" 16 | env : { 17 | key : value 18 | } 19 | cwd : 'Current Working directory' 20 | state: user state during callback. 21 | } 22 | */ 23 | "use strict"; 24 | 25 | //Make it an EventEmitter. 26 | EventEmitter.call(this); 27 | 28 | this.processes = {}; 29 | 30 | } 31 | util.inherits(Spawner, EventEmitter); 32 | 33 | var instance = new Spawner(); 34 | 35 | function startprocess(options) { 36 | 37 | var userstate = options.state; 38 | var args = options.command; 39 | args = args.match(/(?:[^\s"]+|"[^"]*")+/g); 40 | var command = args.shift(); 41 | var cwd = options.cwd || process.cwd; 42 | var env = mergeEnv(options.env); 43 | var spawnOptions = { 44 | cwd: cwd, 45 | env: env, 46 | } 47 | 48 | var proc = spawn(command, args, spawnOptions); 49 | 50 | var procEntry = { 51 | pid: proc.pid, 52 | cmd: command, 53 | process: proc, 54 | options: spawnOptions, 55 | state: userstate, 56 | data: '' 57 | }; 58 | 59 | instance.emit('spawn', procEntry) 60 | 61 | proc.stdout.on('data', function (data) { 62 | procEntry.data = isUtf8(data) ? data.toString() : data; 63 | instance.emit('data', procEntry); 64 | }); 65 | 66 | proc.stderr.on('data', function (data) { 67 | procEntry.data = { 68 | stderr: isUtf8(data) ? data.toString() : new Buffer(data) 69 | } 70 | 71 | instance.emit('data', procEntry); 72 | }); 73 | 74 | proc.on('close', function (code) { 75 | procEntry.exitcode = code; 76 | instance.emit('complete', procEntry); 77 | }); 78 | 79 | proc.on('error', function (code) { 80 | procEntry.exitcode = code; 81 | instance.emit('complete', procEntry); 82 | instance.emit('error', procEntry); 83 | }); 84 | } 85 | 86 | instance.on('data', function (procEntry) { 87 | if (spawnerOptions.verbose) { 88 | if (procEntry.exitcode) { 89 | console.log("Exiting process %s", procEntry.pid || JSON.stringify(procEntry.exitcode)); 90 | } 91 | else { 92 | console.log("%s : %s", procEntry.pid, JSON.stringify(procEntry.data)); 93 | } 94 | } 95 | }); 96 | 97 | instance.on('spawn', function (proc) { 98 | instance.processes[proc.pid] = proc; 99 | }); 100 | 101 | instance.on('complete', function (proc) { 102 | proc.data = JSON.stringify({ exitcode: proc.exitcode }) 103 | instance.emit('data', proc); 104 | if (proc) { 105 | delete instance.processes[proc.pid]; 106 | } 107 | }) 108 | 109 | instance.on('error', function (proc) { 110 | console.log("ERR : %s", JSON.stringify(proc.exitcode)); 111 | }) 112 | 113 | function mergeEnv(env) { 114 | var all = {}; 115 | for (var key in process.env) { 116 | all[key] = process.env[key] 117 | } 118 | if (env) { 119 | for (var k in env) { 120 | all[k] = env[k]; 121 | } 122 | } 123 | return all; 124 | } 125 | 126 | 127 | module.exports.spawn = startprocess; 128 | module.exports.options = spawnerOptions; 129 | -------------------------------------------------------------------------------- /client/libs/utils.js: -------------------------------------------------------------------------------- 1 | 2 | function guid() { 3 | function s4() { 4 | return Math.floor((1 + Math.random()) * 0x10000) 5 | .toString(16) 6 | .substring(1); 7 | } 8 | return s4() + s4() + '-' + s4() + '-' + s4() + '-' + 9 | s4() + '-' + s4() + s4() + s4(); 10 | } 11 | 12 | module.exports.guid = guid; 13 | -------------------------------------------------------------------------------- /client/messageprocessor.js: -------------------------------------------------------------------------------- 1 | var mdparser = require('./libs/mdparser'); 2 | var utils = require('./libs/utils'); 3 | var util = require('util'), 4 | colors = require('colors'); 5 | 6 | var environment = {}; 7 | 8 | function getnextstep(step) { 9 | var step = Number(step); 10 | if (Number.isNaN(step)) { 11 | step = -1; 12 | } 13 | return step + 1; 14 | } 15 | 16 | function flattenEnv(env) { 17 | // Resolve upto 5 levels 18 | for (var i = 0; i < 5; ++i) { 19 | var allSymbolsResolved = true; 20 | for (var property in env) { 21 | if (typeof (env[property]) == 'string') { 22 | env[property] = env[property].replace(/\$\((.*?)\)/g, function (match, $1) { 23 | return env[$1] || match; 24 | }); 25 | if (env[property].indexOf("$(") > -1) { 26 | allSymbolsResolved = false; 27 | } 28 | } 29 | } 30 | if (allSymbolsResolved) { 31 | break; 32 | } 33 | } 34 | return env; 35 | } 36 | 37 | 38 | function resolveEnv(env) { 39 | 40 | for (var target in env["$targets"]) { 41 | env[target] = env["$targets"][target]["name"]; 42 | } 43 | 44 | var resolvedEnv = {}; 45 | for (var target in env["$targets"]) { 46 | for (var property in env) { 47 | if (property == "$targets") { continue; } 48 | if (typeof env["$targets"][target][property] == 'undefined') { 49 | env["$targets"][target][property] = env[property]; 50 | } 51 | } 52 | //env["$targets"][target][target] = env["$targets"][target]["name"]; 53 | resolvedEnv[target] = flattenEnv(env["$targets"][target]); 54 | } 55 | return resolvedEnv; 56 | } 57 | 58 | function getEnvId(callbacktopic, jobid, testid) { 59 | return util.format("%s/%s/%s/%s", callbacktopic, "env", jobid, testid); 60 | } 61 | 62 | function process(in_msg, callbacktopic) { 63 | var msg = in_msg; 64 | if (typeof (in_msg) == 'string') { 65 | try { 66 | msg = JSON.parse(in_msg); 67 | } catch (error) { 68 | console.log(error); 69 | return null; 70 | } 71 | } 72 | 73 | var envid = ""; 74 | 75 | 76 | if (msg.exitcode && msg.exitcode != 0 && msg.continueOnError != 'true') { 77 | console.log('[EndTest ] '); 78 | console.log(colors.red("[Exec+Callback] " + "ERR_EXITCODE" + msg.exitcode)); 79 | return null; 80 | } 81 | else if (msg.spec) { 82 | // This is the flow for the test kickoff 83 | if(msg.step == undefined) { 84 | console.log(colors.blue("[StartTest ] ") + msg.spec); 85 | } 86 | else { 87 | console.log(colors.blue("Invalid message received by controller: (step is defined along with spec) " + msg)); 88 | return null; 89 | } 90 | 91 | if(typeof msg.env == 'undefined') { 92 | console.log(colors.blue("Invalid message received by controller: (env doesn't exist) " + msg)); 93 | return null; 94 | } 95 | if (typeof msg.jobid == 'undefined') { 96 | msg.jobid = utils.guid(); 97 | }; 98 | if (typeof msg.testid == 'undefined') { 99 | msg.testid = utils.guid(); 100 | }; 101 | msg.env["testid"] = msg.testid; 102 | msg.env["jobid"] = msg.jobid; 103 | envid = getEnvId(callbacktopic, msg.jobid,msg.testid); 104 | environment[envid] = resolveEnv(msg.env); 105 | environment[envid+"/spec"] = msg.spec; 106 | } 107 | else if (msg.testid && msg.jobid) { 108 | if (msg.async) { 109 | if (msg.exitcode && msg.exitcode != 0) { 110 | console.log('[async command failed] ' + msg); 111 | return null; 112 | } 113 | if (!msg.continue) { 114 | return null; 115 | } 116 | } 117 | console.log(colors.blue("[ResumeTest ]" + msg.jobid +"/" +msg.testid)); 118 | envid = getEnvId(callbacktopic, msg.jobid,msg.testid); 119 | msg.spec = environment[envid+"/spec"]; 120 | if(typeof msg.spec == 'undefined') { 121 | console.log(colors.blue("[ResumeTestFailed] Environment doesn't exist for " + msg.jobid +"/" +msg.testid)); 122 | return; 123 | } 124 | } 125 | else { 126 | console.log("Invalid message received by controller: (jobid and testid are mandatory): " + JSON.stringify(msg)) 127 | return null; 128 | } 129 | 130 | 131 | 132 | var nextStep = getnextstep(msg.step); 133 | 134 | //Get next command 135 | var cmd = mdparser.getcommand(msg.spec, nextStep, environment[envid]); 136 | if (cmd) { 137 | var result = 138 | { 139 | msg: { 140 | command: cmd.command, 141 | callbacktopic: callbacktopic, 142 | cwd: cmd.cwd, 143 | logfile: cmd.logfile, 144 | step: nextStep, 145 | jobid: msg.jobid, 146 | testid: msg.testid, 147 | async: cmd.async, 148 | continueOnError: cmd.continueOnError, 149 | exitcode: undefined 150 | }, 151 | target: cmd.target 152 | }; 153 | 154 | if (result.msg.step == 0) { 155 | result.env = environment[envid]; 156 | result.setenv = true; 157 | } 158 | return result; 159 | } 160 | 161 | return null; 162 | } 163 | 164 | 165 | module.exports.process = process; 166 | -------------------------------------------------------------------------------- /client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cmdport-client", 3 | "version": "1.0.0", 4 | "description": "Client commands for cmd port", 5 | "main": "cmdport.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+ssh://git@github.com/sajayantony/cmdport.git" 12 | }, 13 | "keywords": [ 14 | "cmdport", 15 | "node-red", 16 | "mqtt" 17 | ], 18 | "author": "Sajay Antony", 19 | "license": "MIT", 20 | "bugs": { 21 | "url": "https://github.com/sajayantony/cmdport/issues" 22 | }, 23 | "homepage": "https://github.com/sajayantony/cmdport#readme", 24 | "dependencies": { 25 | "commist": "^1.0.0", 26 | "concat-stream": "^1.4.7", 27 | "minimist": "^1.1.0", 28 | "help-me": "^0.2.0", 29 | "mqtt": "^1.8.0", 30 | "colors": "~1.1.2", 31 | "sleep": "~3.0.1", 32 | "process": "~0.11.2", 33 | "q": "~1.4.1" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /client/run-coldstart-all.ps1: -------------------------------------------------------------------------------- 1 | param ( 2 | [string] 3 | $config = "run-coldstart.config.json", 4 | 5 | [int] 6 | $timeout = -1 7 | ) 8 | 9 | function RunTest($os, $framework, $appHost, $scenario, $database, $precompileVersion) 10 | { 11 | $params = @{ "os" = $os; "framework" = $framework; "scenario" = $scenario; "timeout" = $timeout } 12 | 13 | if ($appHost) 14 | { 15 | $params.appHost = $appHost 16 | } 17 | 18 | if ($database) 19 | { 20 | $params.database = $database 21 | } 22 | 23 | if ($precompileVersion) 24 | { 25 | $params.precompileVersion = $precompileVersion 26 | } 27 | 28 | & $runTest @params 29 | } 30 | 31 | 32 | $runTest = Join-Path $PSScriptRoot run-coldstart.ps1 33 | 34 | if (! (Test-Path $config)) 35 | { 36 | Write-Error "Config file ${config} does not exist" 37 | Exit -1 38 | } 39 | 40 | $configObject = gc $config | ConvertFrom-Json 41 | 42 | $osList = $configObject.os 43 | $frameworkList = $configObject.framework 44 | $scenarioList = $configObject.scenario 45 | $precompileOptions = $configObject.precompile_options 46 | 47 | $osHostMap = $configObject.os_hosts_support 48 | $osDBMap = $configObject.os_db_support 49 | $dbscenarios = $configObject.db_scenarios 50 | 51 | foreach ($precompileVersion in $precompileOptions) 52 | { 53 | if ($precompileVersion -eq "none") 54 | { 55 | $precompileVersion = $null 56 | } 57 | 58 | foreach ($os in $osList) 59 | { 60 | foreach ($framework in $frameworkList) 61 | { 62 | $hosts = $osHostMap | Select -ExpandProperty $os 63 | 64 | foreach ($appHost in $hosts) 65 | { 66 | # the parser has problem handle null value, use "none" instead for no app host 67 | if ($appHost -eq "none") 68 | { 69 | $appHost = $null 70 | } 71 | 72 | foreach ($scenario in $scenarioList) 73 | { 74 | if ($dbscenarios.Contains($scenario)) 75 | { 76 | $dbList = $osDBMap | Select -ExpandProperty $os 77 | } 78 | else 79 | { 80 | $dbList = @( $null ) 81 | } 82 | 83 | foreach ($database in $dbList) 84 | { 85 | RunTest -os $os -framework $framework -appHost $appHost -scenario $scenario -database $database -precompileVersion $precompileVersion 86 | } 87 | } 88 | } 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /client/run-coldstart.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "os" : [ "win", "linux" ], 3 | "framework" : [ "netcoreapp1.0" ], 4 | "scenario" : [ "musicstore", "mvc", "text" ], 5 | "precompile_options" : [ "none", "1.0.0" ], 6 | "os_hosts_support" : { "win" : [ "none", "iis" ], "linux" : [ "none", "nginx" ] }, 7 | "os_db_support" : { "win" : [ "inmemory", "localdb", "remotedb" ], "linux" : [ "inmemory", "remotedb" ] }, 8 | "db_scenarios" : [ "musicstore" ] 9 | } 10 | -------------------------------------------------------------------------------- /client/run-coldstart.ps1: -------------------------------------------------------------------------------- 1 | param ( 2 | [Parameter(Mandatory=$true)] 3 | [ValidateSet("text","mvc","musicstore")] 4 | $scenario, 5 | 6 | [ValidateSet("inmemory","localdb","remotedb")] 7 | $database, 8 | 9 | [Parameter(Mandatory=$true)] 10 | [ValidateSet("win","linux")] 11 | $os, 12 | 13 | [ValidateSet("iis","nginx")] 14 | $appHost, 15 | 16 | [Parameter(Mandatory=$true)] 17 | [ValidateSet("netcoreapp1.0")] 18 | $framework, 19 | 20 | $precompileVersion, 21 | 22 | [Int] 23 | $timeout = -1 24 | ) 25 | 26 | function CombinePath() 27 | { 28 | if ($os -eq "win") 29 | { 30 | $separator = "\" 31 | } 32 | else 33 | { 34 | $separator = "/" 35 | } 36 | [System.String]::Join($separator, $args) 37 | } 38 | 39 | function AppendScriptExtension($scriptName) 40 | { 41 | if ($os -eq "win") 42 | { 43 | $scriptName + ".ps1" 44 | } 45 | elseif ($os -eq "linux") 46 | { 47 | "./" + $scriptName + ".sh" 48 | } 49 | else 50 | { 51 | Exit -1 52 | } 53 | } 54 | 55 | function AppendIISPostfix($scriptName) 56 | { 57 | if ($appHost -eq "iis") 58 | { 59 | $scriptName + "-IIS" 60 | } 61 | else 62 | { 63 | $scriptName 64 | } 65 | } 66 | 67 | function AppendAppHostPostfix($scriptName) 68 | { 69 | if (!$appHost) 70 | { 71 | $scriptName 72 | } 73 | elseif ($appHost -eq "iis") 74 | { 75 | $scriptName + "-IIS" 76 | } 77 | elseif ($appHost -eq "nginx") 78 | { 79 | $scriptName + "-nginx" 80 | } 81 | else 82 | { 83 | Exit -1 84 | } 85 | } 86 | 87 | $logdirMap = @{ 88 | "win" = "c:\`$(testid)"; 89 | "linux" = "/home/asplab/`$(testid)" 90 | } 91 | 92 | $pathMap = @{ 93 | "win" = "%localappdata%\microsoft\dotnet;%ProgramFiles%\Git\cmd"; 94 | "linux" = "/home/asplab/.dotnet" 95 | } 96 | 97 | $rebootCommandMap = @{ 98 | "win" = "shutdown /r /f /t 0"; 99 | "linux" = "sudo shutdown -r now" 100 | } 101 | 102 | $publishScript = AppendIISPostfix "Publish" 103 | $publishScript = AppendScriptExtension $publishScript 104 | 105 | $measureScript = AppendAppHostPostfix "Measure" 106 | $measureScript = AppendScriptExtension $measureScript 107 | 108 | $archiveScript = AppendAppHostPostfix "Archive" 109 | $archiveScript = AppendScriptExtension $archiveScript 110 | 111 | $serverMap = @{ 112 | "win" = "Asp-xHPc7"; 113 | "linux" = "Asp-xHPSLC1" 114 | } 115 | 116 | if ($appHost) 117 | { 118 | $osHostAbbreviation = $appHost 119 | } 120 | else 121 | { 122 | $osHostAbbreviation = $os 123 | } 124 | 125 | if ($framework -eq "netcoreapp1.0") 126 | { 127 | $frameworkAbbreviation = "core" 128 | } 129 | else 130 | { 131 | $frameworkAbbreviation = $framework 132 | } 133 | 134 | $appMap = @{ 135 | "musicstore" = "MusicStore"; 136 | "mvc" = "HelloWorldMvc"; 137 | "text" = "BasicKestrel" 138 | } 139 | 140 | if ($os -eq "win") 141 | { 142 | $gitHome = "D:\git\aspnet" 143 | } 144 | else 145 | { 146 | $gitHome = "/home/asplab/git/aspnet" 147 | } 148 | 149 | $perfHome = CombinePath $gitHome "Performance" 150 | $scriptHome = CombinePath $perfHome "test" "ColdStart" 151 | 152 | if ($database -eq "inmemory") 153 | { 154 | $testAppBranch = "shhsu/coldstart_inmemory" 155 | } 156 | elseif ($database -eq "remotedb") 157 | { 158 | $testAppBranch = "shhsu/coldstart_remote_sql2016" 159 | } 160 | else 161 | { 162 | $testAppBranch = "shhsu/coldstart" 163 | ## musicstore localdb or non-database scenarios 164 | } 165 | 166 | if ($scenario -eq "musicstore") 167 | { 168 | $testAppSource = "https://github.com/aspnet/MusicStore.git" 169 | $testAppHome = CombinePath $gitHome "MusicStore" 170 | $testAppDir = CombinePath $testAppHome "samples" "MusicStore" 171 | } 172 | else 173 | { 174 | $testAppSource = "https://github.com/aspnet/Performance.git" 175 | $testAppHome = $perfHome 176 | $testAppDir = "null" 177 | } 178 | 179 | if ($precompileVersion) 180 | { 181 | $precompileOption = "-p ${precompileVersion}" 182 | } 183 | else 184 | { 185 | # mdparser actually ignores empty string for some reason (ln 109: return env[$1] || match) 186 | $precompileOption = " " 187 | } 188 | 189 | $testName = "${scenario}" 190 | 191 | if ($database) 192 | { 193 | $testName += "-${database}" 194 | } 195 | 196 | $testName += "-${osHostAbbreviation}-${frameworkAbbreviation}" 197 | 198 | if ($precompileVersion) 199 | { 200 | $testName += "-precompile-${precompileVersion}" 201 | } 202 | 203 | $scenarioObj = @{ 204 | "logdir" = $logdirMap.Get_Item($os); 205 | "path" = $pathMap.Get_Item($os); 206 | "rebootCommand" = $rebootCommandMap.Get_Item($os); 207 | "measureScript" = $measureScript; 208 | "publishScript" = $publishScript; 209 | "archiveScript" = $archiveScript; 210 | "`$targets" = @{ 211 | "server" = @{ 212 | "name" = $serverMap.Get_Item($os); 213 | "testName" = $testName; 214 | "targetApp" = $appMap.Get_Item($scenario); 215 | "framework" = $framework; 216 | "precompileOption" = $precompileOption; 217 | "gitHome" = $gitHome; 218 | "scriptSource" = "https://github.com/aspnet/Performance.git"; 219 | "perfHome" = $perfHome; 220 | "perfBranch" = "shhsu/coldstart"; 221 | "scriptHome" = $scriptHome; 222 | "testAppBranch" = $testAppBranch; 223 | "testAppSource" = $testAppSource; 224 | "testAppHome" = $testAppHome; 225 | "testAppDir" = $testAppDir 226 | } 227 | } 228 | } 229 | 230 | $outputFileName = "run-coldstart-${testName}.json" 231 | 232 | $coldstartTestsDir = [System.IO.Path]::Combine($PSScriptRoot, "test", "coldstart") 233 | $scenarioFile = [System.IO.Path]::Combine($coldstartTestsDir, $outputFileName) 234 | ConvertTo-Json $scenarioObj | Out-File $scenarioFile -Encoding Default 235 | 236 | $jobfile = [System.IO.Path]::Combine($coldstartTestsDir, "run-job-coldstart.json") 237 | ConvertTo-Json @{ $testName = @{ spec = "./test/coldstart/run-coldstart.md" ; env = "./test/coldstart/${outputFileName}" }} ` 238 | | Out-File $jobfile -Encoding Default 239 | 240 | $program = [System.IO.Path]::Combine($PSScriptRoot, "controller.js") 241 | $hostname = & hostname 242 | 243 | $nodeProc = Start-Process "node" -ArgumentList "${program} --job ${jobfile} --topic controller/${hostname} --verbose" -PassThru 244 | 245 | if ($timeout -gt 0) 246 | { 247 | Start-Sleep -s $timeout 248 | } 249 | else 250 | { 251 | Read-Host "Press enter to run the next test..." 252 | } 253 | 254 | Stop-Process $nodeProc 255 | -------------------------------------------------------------------------------- /client/test/coldstart/README.md: -------------------------------------------------------------------------------- 1 | *** Running coldstart tests 2 | 3 | Run ${WaveHome}\client\run-coldstart-all.ps1 to test all tracked coldstart scenario. 4 | You can also create a new config file run-coldstart.config.json to change which scenarios to run 5 | 6 | Run run-coldstart.ps1 to test a single scenario 7 | 8 | Due to the current design limiation, tests have to be run in sequence. There is no way for the test to notify caller that it has completed. 9 | The tests can take a timeout parameter or user input so it knows when to stop the current test and run the next test. 10 | 11 | *** Music Store DB Connections 12 | 13 | For Music Store remote DB scenario, an environment variables aspnet_test_musicstore_dbid and aspnet_test_musicstore_dbpw needs to be set up on the lab machine via SetEnv 14 | command. These variables specifies the database username and password that should be use to connect. 15 | 16 | For IIS scenario, SetEnv does not work. the variables should be set golbally to take effect. 17 | 18 | *** Know issues 19 | 20 | IIS scenario sometimes fails. Broker sometimes does not receive the completion notification for shutdown command (pending investigations). 21 | 22 | When error occurs, the test needs to be manually stopped. Remove the IIS sites, publish directory and current test directory and rerun. 23 | -------------------------------------------------------------------------------- /client/test/coldstart/run-coldstart.md: -------------------------------------------------------------------------------- 1 | # Startup scenario. Run 3 iterations of tests and archive the results 2 | 3 | | Command | Host |Description| 4 | |-------------|-----------|-----------| 5 | | `git clone -b shhsu/coldstart $(scriptSource)` | $(server) | Clone the scripts repo | 6 | | `git fetch --all` | $(server) | Fetch from git | 7 | | `git checkout $(perfBranch)` | $(server) | Checkout, in case the clone command failed due to dir exists | 8 | | `git clean -xdf` | $(server) | Cleanup local perf repo | 9 | | `git reset --hard origin/$(perfBranch)` | $(server) | Reset to coldstart branch | 10 | | `git clone -b $(testAppBranch) $(testAppSource)` | $(server) | Clone the test apps repo | 11 | | `git fetch --all` | $(server) | Fetch from git | 12 | | `git checkout $(testAppBranch)` | $(server) | Checkout, in case the clone command failed due to dir exists | 13 | | `git clean -xdf` | $(server) | Cleanup local test app repo | 14 | | `git reset --hard origin/$(testAppBranch)` | $(server) | Reset to coldstart branch | 15 | | `$(publishScript) -t $(targetApp) -f $(framework) -d $(testAppDir) $(precompileOption)` | $(server) | Publish App | 16 | | `$(rebootCommand)` | $(server) | Reboot | 17 | | `$(measureScript) -t $(targetApp) -f $(framework)` | $(server) | Measure Iteration 1 | 18 | | `$(rebootCommand)` | $(server) | Reboot | 19 | | `$(measureScript) -t $(targetApp) -f $(framework)` | $(server) | Measure Iteration 2 | 20 | | `$(rebootCommand)` | $(server) | Reboot | 21 | | `$(measureScript) -t $(targetApp) -f $(framework)` | $(server) | Measure Iteration 3 | 22 | | `$(archiveScript) -t $(targetApp) -f $(framework) -n $(testName)` | $(server) | Archive Test Results | 23 | -------------------------------------------------------------------------------- /client/test/run-helloworldmvc-env.json: -------------------------------------------------------------------------------- 1 | { 2 | "logdir": "c:\\$(testid)", 3 | "path" : "%localappdata%\\microsoft\\dotnet;%ProgramFiles%\\Git\\cmd", 4 | "basepath" : "c:\\$(testid)", 5 | "scriptSource": "https://github.com/aspnet/Performance.git", 6 | "appDll": "HelloWorldMvc.dll", 7 | "buildPath": "", 8 | "$targets": { 9 | "server": { 10 | "name": "wincell1-win1" 11 | }, 12 | "client": { 13 | "name": "wincell1-win2" 14 | } 15 | } 16 | } -------------------------------------------------------------------------------- /client/test/run-helloworldmvc.md: -------------------------------------------------------------------------------- 1 | # HelloWorldMvc 2 | ## Test 3 | 4 | | Command | Host |Description| 5 | |-------------|-----------|-----------| 6 | | `git clone -b brecon/hosting $(scriptSource)` | $(server) | Clone the repo | 7 | | `build.ps1 clean` | $(server) | Install CLI | 8 | | `c:\users\asplab\appdata\local\microsoft\dotnet\dotnet.exe restore --infer-runtimes` | $(server) | Restore packages | 9 | | `c:\users\asplab\appdata\local\microsoft\dotnet\dotnet.exe publish -c release` | $(server) | Restore packages | 10 | | `c:\users\asplab\appdata\local\microsoft\dotnet\dotnet.exe .\$(appdll)` | $(server) | Run Server | 11 | 12 | -------------------------------------------------------------------------------- /client/test/run-job-helloworldmvc.json: -------------------------------------------------------------------------------- 1 | { 2 | "HelloWorldMvc" : { 3 | "testspec" : "./test/run-helloworldmvc-win.md", 4 | "testenv": "./test/run-helloworldmvc-win-env.json" 5 | } 6 | } -------------------------------------------------------------------------------- /client/test/run-job-musicstore.json: -------------------------------------------------------------------------------- 1 | { 2 | "HelloWorldMvc" : { 3 | "spec" : "./test/run-musicstore.md", 4 | "env": "./test/run-musicstore-env.json" 5 | } 6 | } -------------------------------------------------------------------------------- /client/test/run-localhost-env.json: -------------------------------------------------------------------------------- 1 | { 2 | "server": "$(localhost)", 3 | "machines" :"$(localhost)" 4 | } -------------------------------------------------------------------------------- /client/test/run-localhost.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 'use strict'; 3 | 4 | var os = require('os'); 5 | var path = require('path'); 6 | var cmdport = require('../cmdport'); 7 | var controller = require('../controller'); 8 | var env = require('./test-localhost-env.json') 9 | 10 | // Add localhost variable. 11 | env.localhost = os.hostname().toLowerCase(); 12 | 13 | var controllertopic = ('job/' + os.hostname()).toLowerCase(); 14 | var payload = { 15 | testspec : path.resolve(__dirname, './test-localhost.md'), 16 | env: env 17 | }; 18 | 19 | controller.start(process.argv.slice(2).concat("--verbose")) 20 | cmdport.send(controllertopic, payload); 21 | setTimeout(function() { 22 | console.log("Exiting ...") 23 | }, 5000); 24 | return; -------------------------------------------------------------------------------- /client/test/run-localhost.md: -------------------------------------------------------------------------------- 1 | # Test 2 | 3 | | Command | Host |Description| 4 | |-------------|-----------|-----------| 5 | | `hostname` | $(server) |server command| 6 | | `ifconfig /?` | $(server) |server command| 7 | | `hostname` | $(server) |server command| 8 | -------------------------------------------------------------------------------- /client/test/run-musicstore-env.json: -------------------------------------------------------------------------------- 1 | { 2 | "scriptSource": "https://github.com/aspnet/Musicstore.git", 3 | "appDll": "musicstore.dll", 4 | "buildPath": "", 5 | "UseDatabase": "false", 6 | "$targets": { 7 | "server": { 8 | "name": "testcell-win1", 9 | "basepath" : "c:\\$(testid)", 10 | "logdir": "c:\\$(testid)", 11 | "$path" : "c:\\users\\asplab\\appdata\\local\\microsoft\\dotnet;%ProgramFiles%\\Git\\cmd;%ProgramFles%\\nodejs" 12 | }, 13 | "client": { 14 | "name": "testcell-linux1", 15 | "basepath": "/home/asplab", 16 | "logdir": "/home/asplab/$(testid)" 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /client/test/run-musicstore.md: -------------------------------------------------------------------------------- 1 | # HelloWorldMvc 2 | ## Test 3 | 4 | | Command | Host |Description| 5 | |-------------|-----------|-----------| 6 | | `git clone -b sivagms/perf $(scriptSource)` | $(server) | Clone the repo | 7 | | `build.ps1 clean` | $(server) | Install CLI | 8 | | `dotnet.exe restore --infer-runtimes` | $(server) | Restore packages | 9 | | `dotnet.exe run --server.urls=http://$(server):5000` | $(server) | Launch the server. | 10 | | `npm install loadtest -g` | $(client) | Install pacakges for load test. | 11 | | `loadtest -n 1000 -c 10 http://$(server):5000` | $(client) | Kick off the loadtest. | 12 | -------------------------------------------------------------------------------- /client/test/run-spawner.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var isWin = /^win/.test(process.platform); 4 | var spawner = require('../libs/spawner.js'); 5 | spawner.options.verbose = true; 6 | 7 | var procInfo = {}; 8 | 9 | if (isWin) { 10 | procInfo = { 11 | command: "cmd.exe /c set TEST && dir explorer.exe /b", 12 | cwd: "c:/windows", 13 | env: { 14 | TEST: "RandomTestValue" 15 | } 16 | }; 17 | } 18 | else { 19 | procInfo = { 20 | command: "printenv TEST", 21 | cwd: "/usr", 22 | env: { 23 | "TEST": "RandomNonWindowsValue" 24 | } 25 | } 26 | } 27 | function clone(a) { 28 | return JSON.parse(JSON.stringify(a)); 29 | } 30 | 31 | spawner.spawn(clone(procInfo)); 32 | 33 | procInfo.env.TEST = "test2"; 34 | spawner.spawn(clone(procInfo)); 35 | 36 | procInfo.env.TEST = "test3"; 37 | spawner.spawn(clone(procInfo)); 38 | 39 | 40 | -------------------------------------------------------------------------------- /client/test/testenv.json: -------------------------------------------------------------------------------- 1 | { 2 | "machines": "$(server),$(client)", 3 | "server": "wincell1-win1", 4 | "client": "wincell1-win2" 5 | } -------------------------------------------------------------------------------- /client/web/agent.js: -------------------------------------------------------------------------------- 1 | 2 | ko.safeObservable = function (initialValue) { 3 | var result = ko.observable(initialValue); 4 | result.safe = ko.dependentObservable(function () { 5 | return result() || {}; 6 | }); 7 | 8 | return result; 9 | }; 10 | 11 | 12 | function Machine(payload) { 13 | var self = this; 14 | try { 15 | self.config = JSON.parse(payload); 16 | self.name = self.config.clientid; 17 | self.output = ko.observable(""); 18 | 19 | var outputelement = document.getElementById("outputWindow"); 20 | self.output.subscribe(function (value) { 21 | outputelement.scrollTop = outputelement.scrollHeight; 22 | }); 23 | } catch (e) { 24 | self.config = {}; 25 | self.name = 'Unknown'; 26 | } 27 | }; 28 | 29 | var ViewModel = function () { 30 | var self = this; 31 | var creds = _creds; 32 | 33 | self.Input = ko.observable(); 34 | self.CurrentNode = ko.safeObservable(); 35 | self.Machines = ko.observableArray(); 36 | self.broker = ko.observable(); 37 | self.username = ko.observable(); 38 | self.password = ko.observable(); 39 | self.port = ko.observable(443); 40 | self.connected = ko.observable(false); 41 | self.broker((creds && creds.broker) ? creds.broker.host : "broker"); 42 | self.username((creds && creds.broker) ? creds.broker.username : "admin"); 43 | self.password((creds && creds.broker) ? creds.broker.password : ""); 44 | self.port((creds && creds.broker.port) ? parseInt(creds.broker.port) + 1 : 443); 45 | 46 | var command = { 47 | command: "hostname" 48 | }; 49 | self.Command = ko.observable(JSON.stringify(command, null, 4)); 50 | 51 | //var broker = data.broker; 52 | function Subscribe() { 53 | // Create a client instance 54 | 55 | client = new Paho.MQTT.Client(self.broker(), 1884, guid()); 56 | client = new Paho.MQTT.Client(self.broker(), parseInt(self.port()), guid()); 57 | 58 | // set callback handlers 59 | client.onConnectionLost = onConnectionLost; 60 | client.onMessageArrived = onMessageArrived; 61 | 62 | // connect the client 63 | function connect() { 64 | client.connect({ 65 | onSuccess: onConnect, 66 | onFailure: onFailure, 67 | userName: self.username(), 68 | password: self.password(), 69 | useSSL: false 70 | }); 71 | } 72 | 73 | function onFailure(err) { 74 | console.log(err); 75 | } 76 | 77 | // called when the client connects 78 | function onConnect() { 79 | self.connected(true); 80 | 81 | // Once a connection has been made, make a subscription and send a message. 82 | console.log("onConnect"); 83 | client.subscribe("client/+/config"); 84 | 85 | //message = new Paho.MQTT.Message("Hello"); 86 | //message.destinationName = "/world"; 87 | //client.send(message); 88 | } 89 | 90 | // called when the client loses its connection 91 | function onConnectionLost(responseObject) { 92 | if (responseObject.errorCode !== 0) { 93 | console.log("onConnectionLost:" + responseObject.errorMessage); 94 | } 95 | self.connected(false); 96 | } 97 | 98 | connect(); 99 | 100 | // called when a message arrives 101 | function onMessageArrived(message) { 102 | var configIndex = message.destinationName.indexOf("/config") 103 | if (configIndex > -1) { 104 | var machineName = message.destinationName.substring(7, configIndex); 105 | var msg = new Machine(message.payloadString); 106 | var match = ko.utils.arrayFirst(self.Machines(), function (item) { 107 | return item.name === machineName; 108 | }); 109 | if (msg.name === "Unknown") { 110 | self.Machines.remove(match); 111 | } else if (match) { 112 | self.Machines.replace(match, msg); 113 | } 114 | else { 115 | self.Machines.push(msg); 116 | } 117 | } 118 | var outIndex = message.destinationName.indexOf("output") 119 | if (outIndex > -1) { 120 | var match = ko.utils.arrayFirst(self.Machines(), function (item) { 121 | return message.destinationName.substring(0, outIndex - 1) === item.name; 122 | }); 123 | match.output(match.output() + message.payloadString); 124 | } 125 | console.log("onMessageArrived: (" + message.destinationName + ")" + message.payloadString); 126 | } 127 | return client; 128 | 129 | } 130 | self.authVisible = ko.observable(true); 131 | self.cmdVisible = ko.observable(false); 132 | 133 | self.Login = function () { 134 | self.Machines.removeAll(); 135 | self.client = Subscribe(); 136 | self.cmdVisible(true) 137 | } 138 | 139 | function guid() { 140 | function s4() { 141 | return Math.floor((1 + Math.random()) * 0x10000) 142 | .toString(16) 143 | .substring(1); 144 | } 145 | return s4() + s4() + '-' + s4() + '-' + s4() + '-' + 146 | s4() + '-' + s4() + s4() + s4(); 147 | } 148 | self.Send = function () { 149 | var machine = self.CurrentNode(); 150 | var command = preProcessCommand(self.Command(), machine.config.os === "win32"); 151 | console.log("Send - [" + machine.name + "] " + command); 152 | var msg = new Paho.MQTT.Message(command); 153 | msg.destinationName = self.CurrentNode().name; 154 | self.client.send(msg); 155 | machine.output(machine.output() + "\n"); 156 | }; 157 | 158 | var currentSubscriptions = []; 159 | self.onNodeClick = function (machine) { 160 | 161 | currentSubscriptions.forEach(function (sub) { 162 | self.client.unsubscribe(sub); 163 | }); 164 | //self.Input(""); 165 | self.CurrentNode(machine); 166 | self.client.subscribe(machine.name); 167 | self.client.subscribe(machine.name + "/output"); 168 | }; 169 | 170 | self.isNodeChosen = function (machine) { 171 | return self.CurrentNode() === machine; 172 | } 173 | 174 | /* 175 | Handle simple commands and prefix shell to make testing easier. 176 | */ 177 | function preProcessCommand(command, isWin32) { 178 | 179 | var isSimple = true; 180 | var cmd = command; 181 | 182 | try { 183 | cmd = JSON.parse(command); 184 | isSimple = false; 185 | } catch (e) { } 186 | 187 | if (isSimple) { 188 | // This means its a string command; 189 | var arg = command.match(/(?:[^\s"]+|"[^"]*")+/g); 190 | var cmd = arg.shift(); 191 | 192 | if (isChangeDirectory(command)) { 193 | var cdCommand = { 194 | command: "setenv", 195 | options: { 196 | cwd: arg[0] || "" 197 | } 198 | } 199 | 200 | command = JSON.stringify(cdCommand); 201 | 202 | } else if (isWin32) { 203 | var hasShell = cmd.match(/^cmd/i) || cmd.match(/^powershell/i); 204 | var isExe = cmd.match(/\.exe$/i); 205 | if (!hasShell && !isExe) { 206 | command = "cmd.exe /c " + command; 207 | } 208 | } 209 | } 210 | 211 | return command; 212 | }; 213 | function isChangeDirectory(cmd) { 214 | return (cmd.match(/cd/i)); 215 | } 216 | if (self.Machines().length > 0) { 217 | self.CurrentNode(self.Machines()[0]); 218 | } 219 | 220 | }; 221 | 222 | $(document).ready(function () { 223 | var model = new ViewModel(); 224 | ko.applyBindings(model); 225 | }); 226 | 227 | -------------------------------------------------------------------------------- /client/web/shoot.css: -------------------------------------------------------------------------------- 1 | /* 2 | * Base structure 3 | */ 4 | 5 | /* Move down content because we have a fixed navbar that is 50px tall */ 6 | body { 7 | padding-top: 50px; 8 | } 9 | 10 | 11 | /* 12 | * Global add-ons 13 | */ 14 | 15 | .sub-header { 16 | padding-bottom: 10px; 17 | border-bottom: 1px solid #eee; 18 | } 19 | 20 | /* 21 | * Top navigation 22 | * Hide default border to remove 1px line. 23 | */ 24 | .navbar-fixed-top { 25 | border: 0; 26 | } 27 | 28 | /* 29 | * Sidebar 30 | */ 31 | 32 | /* Hide for mobile, show later */ 33 | .sidebar { 34 | display: none; 35 | } 36 | @media (min-width: 768px) { 37 | .sidebar { 38 | position: fixed; 39 | top: 51px; 40 | bottom: 0; 41 | left: 0; 42 | z-index: 1000; 43 | display: block; 44 | padding: 20px; 45 | overflow-x: hidden; 46 | overflow-y: auto; /* Scrollable contents if viewport is shorter than content. */ 47 | background-color: #f5f5f5; 48 | border-right: 1px solid #eee; 49 | } 50 | } 51 | 52 | /* Sidebar navigation */ 53 | .nav-sidebar { 54 | margin-right: -21px; /* 20px padding + 1px border */ 55 | margin-bottom: 20px; 56 | margin-left: -20px; 57 | } 58 | .nav-sidebar > li > a { 59 | padding-right: 20px; 60 | padding-left: 20px; 61 | } 62 | 63 | .nav-sidebar > .header > a, 64 | .nav-sidebar > .header > a:hover, 65 | .nav-sidebar > .header > a:focus { 66 | color: #fff; 67 | background-color: #428bca; 68 | text-transform:uppercase; 69 | } 70 | 71 | .nav-sidebar > .active a::before { 72 | content: "\27A4"; 73 | padding-right:5px; 74 | 75 | float:left; 76 | margin-left:8px; 77 | } 78 | 79 | .nav-sidebar > .active > a, 80 | .nav-sidebar > .active > a:hover, 81 | .nav-sidebar > .active > a:focus { 82 | color: #fff; 83 | padding-left:10px; 84 | background-color: #428bca; 85 | } 86 | 87 | .sub-header { 88 | padding-bottom: 0px; 89 | border-bottom: 4px solid #eee; 90 | } 91 | 92 | /* 93 | * Main content 94 | */ 95 | 96 | .main { 97 | padding: 20px; 98 | } 99 | @media (min-width: 768px) { 100 | .main { 101 | padding-right: 40px; 102 | padding-left: 40px; 103 | } 104 | } 105 | .main .page-header { 106 | margin-top: 0; 107 | } 108 | 109 | 110 | .text-ellipsis{ 111 | white-space: nowrap; 112 | overflow: hidden; 113 | text-overflow: ellipsis; /* This is where the magic happens! */ 114 | } 115 | 116 | .text-ellipsis:hover{ 117 | white-space: normal; 118 | } 119 | 120 | textarea.command{ 121 | resize: none; 122 | font-family : 'Consolas', 'Courier New'; 123 | } 124 | 125 | #outputWindow { 126 | background:rgb(19,39,56); 127 | /*color: rgb(247,252,255);*/ 128 | color: #fff; 129 | font-family: "Consolas", "Courier New"; 130 | height:75%; 131 | } 132 | 133 | .terminal{ 134 | padding-bottom:10px; 135 | } 136 | 137 | .fill{ 138 | height:100%; 139 | } 140 | -------------------------------------------------------------------------------- /config.js: -------------------------------------------------------------------------------- 1 | var os = require('os') 2 | var credUtil = require('./libs/credUtil'); 3 | 4 | try { 5 | // a path we KNOW might not exists since it might not be configured. 6 | var credentials = require('./client/_creds'); 7 | 8 | // Docker containers might have environment variables set and we need to clear them out. 9 | credUtil.clearEnv(); 10 | } 11 | catch (e) { 12 | console.log('Setup credentials using setup.js'); 13 | return; 14 | } 15 | 16 | try { 17 | var ver = require('./artifacts/version.json') 18 | } 19 | catch (e) { 20 | // ... 21 | } 22 | 23 | var config = {}; 24 | config.broker = credentials.broker; 25 | config.clientid = credentials.clientid || os.hostname().toLowerCase(); 26 | config.version = ver || {}; 27 | module.exports = config; -------------------------------------------------------------------------------- /flows/.gitignore: -------------------------------------------------------------------------------- 1 | *.backup 2 | .config.json 3 | -------------------------------------------------------------------------------- /flows/flows_Dispatcher_cred.json: -------------------------------------------------------------------------------- 1 | { 2 | "b788e70f.487718": { 3 | "user": "{mqtt_dynamic.broker_username}", 4 | "password": "{mqtt_dynamic.broker_password}" 5 | }, 6 | "73cb19d6.de22d8": { 7 | "user": "{mqtt_dynamic.broker_username}", 8 | "password": "{mqtt_dynamic.broker_password}" 9 | } 10 | } -------------------------------------------------------------------------------- /infra/AzureTestCellDeployment/1Linux_CellDeployment.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", 3 | "contentVersion": "1.0.0.0", 4 | "parameters": { 5 | "adminUsername": { 6 | "type": "string", 7 | "minLength": 1, 8 | "metadata": { 9 | "description": "User name for the Virtual Machine." 10 | } 11 | }, 12 | "adminPassword": { 13 | "type": "string", 14 | "metadata": { 15 | "description": "Password for the Virtual Machine." 16 | } 17 | }, 18 | "dnsNamePrefix": { 19 | "type": "string", 20 | "minLength": 1, 21 | "maxLength": 100, 22 | "metadata": { 23 | "description": "Globally unique DNS Name prefix to which the VM names will be appended for to access the Virtual Machine." 24 | } 25 | }, 26 | "ubuntuOSVersion": { 27 | "type": "string", 28 | "defaultValue": "14.04.4-LTS", 29 | "metadata": { 30 | "description": "The Ubuntu version for the VM. This will pick a fully patched image of this given Ubuntu version." 31 | } 32 | } 33 | }, 34 | "variables": { 35 | "globalNetworkSecurityGroupName": "globalNetworkSecurityGroup", 36 | "networkSecurityGroupName": "networkSecurityGroup", 37 | "imagePublisher": "Canonical", 38 | "imageOffer": "UbuntuServer", 39 | "OSDiskName": "osdiskforlinux", 40 | "linuxNicName": "LinuxNic", 41 | "addressPrefix": "10.0.0.0/16", 42 | "subnetName": "Subnet", 43 | "subnetPrefix": "10.0.0.0/24", 44 | "vhdStorageType": "Standard_LRS", 45 | "publicIPAddressType": "Dynamic", 46 | "linuxPublicIPAddressName": "LinuxPublicIP", 47 | "linuxVmName": "Linux1", 48 | "linuxPublicDnsName": "[toLower(concat(parameters('dnsNamePrefix'),'-',variables('linuxVmName')))]", 49 | "vhdStorageContainerName": "vhds", 50 | "linuxVmSize": "Standard_A2", 51 | "virtualNetworkName": "VNET", 52 | "vnetId": "[resourceId('Microsoft.Network/virtualNetworks', variables('virtualNetworkName'))]", 53 | "subnetRef": "[concat(variables('vnetId'), '/subnets/', variables('subnetName'))]", 54 | "vhdStorageName": "[concat('vhdstorage', uniqueString(resourceGroup().id))]", 55 | "hostDNSNameScriptArgument": "[concat('*.',resourceGroup().location,'.cloudapp.azure.com')]" 56 | }, 57 | "resources": [ 58 | { 59 | "type": "Microsoft.Storage/storageAccounts", 60 | "name": "[variables('vhdStorageName')]", 61 | "apiVersion": "2015-06-15", 62 | "location": "[resourceGroup().location]", 63 | "tags": { 64 | "displayName": "StorageAccount" 65 | }, 66 | "properties": { 67 | "accountType": "[variables('vhdStorageType')]" 68 | } 69 | }, 70 | { 71 | "apiVersion": "2015-06-15", 72 | "type": "Microsoft.Network/publicIPAddresses", 73 | "name": "[variables('linuxPublicIPAddressName')]", 74 | "location": "[resourceGroup().location]", 75 | "tags": { 76 | "displayName": "LinuxPublicIPAddress" 77 | }, 78 | "properties": { 79 | "publicIPAllocationMethod": "[variables('publicIPAddressType')]", 80 | "dnsSettings": { 81 | "domainNameLabel": "[variables('linuxPublicDnsName')]" 82 | } 83 | } 84 | }, 85 | { 86 | "type": "Microsoft.Network/networkSecurityGroups", 87 | "name": "[variables('networkSecurityGroupName')]", 88 | "apiVersion": "2015-06-15", 89 | "location": "[resourceGroup().location]", 90 | "tags": { 91 | "displayName": "NetworkSecurityGroup" 92 | }, 93 | "properties": { 94 | "securityRules": [ 95 | { 96 | "name": "allow_rdp", 97 | "properties": { 98 | "description": "Allow RDP", 99 | "protocol": "Tcp", 100 | "sourcePortRange": "*", 101 | "destinationPortRange": "3389", 102 | "sourceAddressPrefix": "Internet", 103 | "destinationAddressPrefix": "*", 104 | "access": "Allow", 105 | "priority": 100, 106 | "direction": "Inbound" 107 | } 108 | }, 109 | { 110 | "name": "allow_ssh", 111 | "properties": { 112 | "description": "Allow RDP", 113 | "protocol": "Tcp", 114 | "sourcePortRange": "*", 115 | "destinationPortRange": "22", 116 | "sourceAddressPrefix": "Internet", 117 | "destinationAddressPrefix": "*", 118 | "access": "Allow", 119 | "priority": 101, 120 | "direction": "Inbound" 121 | } 122 | }, 123 | 124 | { 125 | "name": "allow_remote_powershell_http", 126 | "properties": { 127 | "description": "Allow Remote Powershell HTTP", 128 | "protocol": "Tcp", 129 | "sourcePortRange": "*", 130 | "destinationPortRange": "5985", 131 | "sourceAddressPrefix": "Internet", 132 | "destinationAddressPrefix": "*", 133 | "access": "Allow", 134 | "priority": 102, 135 | "direction": "Inbound" 136 | } 137 | }, 138 | { 139 | "name": "allow_remote_powershell_https", 140 | "properties": { 141 | "description": "Allow Remote Powershell HTTPS", 142 | "protocol": "Tcp", 143 | "sourcePortRange": "*", 144 | "destinationPortRange": "5986", 145 | "sourceAddressPrefix": "Internet", 146 | "destinationAddressPrefix": "*", 147 | "access": "Allow", 148 | "priority": 103, 149 | "direction": "Inbound" 150 | } 151 | }, 152 | 153 | 154 | { 155 | "name": "allow_web", 156 | "properties": { 157 | "description": "Allow WEB", 158 | "protocol": "Tcp", 159 | "sourcePortRange": "*", 160 | "destinationPortRange": "80-85", 161 | "sourceAddressPrefix": "Internet", 162 | "destinationAddressPrefix": "*", 163 | "access": "Allow", 164 | "priority": 104, 165 | "direction": "Inbound" 166 | } 167 | }, 168 | { 169 | "name": "allow_web_proxy", 170 | "properties": { 171 | "description": "Allow WEB Proxy", 172 | "protocol": "Tcp", 173 | "sourcePortRange": "*", 174 | "destinationPortRange": "8080", 175 | "sourceAddressPrefix": "Internet", 176 | "destinationAddressPrefix": "*", 177 | "access": "Allow", 178 | "priority": 105, 179 | "direction": "Inbound" 180 | } 181 | }, 182 | { 183 | "name": "allow_kestrel", 184 | "properties": { 185 | "description": "Allow Kestrel", 186 | "protocol": "Tcp", 187 | "sourcePortRange": "*", 188 | "destinationPortRange": "5000-5010", 189 | "sourceAddressPrefix": "Internet", 190 | "destinationAddressPrefix": "*", 191 | "access": "Allow", 192 | "priority": 106, 193 | "direction": "Inbound" 194 | } 195 | } 196 | ] 197 | } 198 | }, 199 | { 200 | "apiVersion": "2015-06-15", 201 | "type": "Microsoft.Network/virtualNetworks", 202 | "name": "[variables('virtualNetworkName')]", 203 | "location": "[resourceGroup().location]", 204 | "tags": { 205 | "displayName": "VirtualNetwork" 206 | }, 207 | "dependsOn": [ 208 | "[concat('Microsoft.Network/networkSecurityGroups/', variables('networkSecurityGroupName'))]" 209 | ], 210 | "properties": { 211 | "addressSpace": { 212 | "addressPrefixes": [ 213 | "[variables('addressPrefix')]" 214 | ] 215 | }, 216 | "subnets": [ 217 | { 218 | "name": "[variables('subnetName')]", 219 | "properties": { 220 | "addressPrefix": "[variables('subnetPrefix')]", 221 | "networkSecurityGroup": { 222 | "id": "[resourceId('Microsoft.Network/networkSecurityGroups', variables('networkSecurityGroupName'))]" 223 | } 224 | } 225 | } 226 | ] 227 | } 228 | }, 229 | { 230 | "apiVersion": "2015-06-15", 231 | "type": "Microsoft.Network/networkInterfaces", 232 | "name": "[variables('linuxNicName')]", 233 | "location": "[resourceGroup().location]", 234 | "tags": { 235 | "displayName": "LinuxNic" 236 | }, 237 | "dependsOn": [ 238 | "[concat('Microsoft.Network/publicIPAddresses/', variables('linuxPublicIPAddressName'))]", 239 | "[concat('Microsoft.Network/virtualNetworks/', variables('virtualNetworkName'))]" 240 | ], 241 | "properties": { 242 | "ipConfigurations": [ 243 | { 244 | "name": "ipconfig1", 245 | "properties": { 246 | "privateIPAllocationMethod": "Dynamic", 247 | "publicIPAddress": { 248 | "id": "[resourceId('Microsoft.Network/publicIPAddresses', variables('linuxPublicIPAddressName'))]" 249 | }, 250 | "subnet": { 251 | "id": "[variables('subnetRef')]" 252 | } 253 | } 254 | } 255 | ] 256 | } 257 | }, 258 | { 259 | "apiVersion": "2015-06-15", 260 | "type": "Microsoft.Compute/virtualMachines", 261 | "name": "[variables('linuxVmName')]", 262 | "location": "[resourceGroup().location]", 263 | "tags": { 264 | "displayName": "LinuxVM" 265 | }, 266 | "dependsOn": [ 267 | "[concat('Microsoft.Storage/storageAccounts/', variables('vhdStorageName'))]", 268 | "[concat('Microsoft.Network/networkInterfaces/', variables('linuxNicName'))]" 269 | ], 270 | "properties": { 271 | "hardwareProfile": { 272 | "vmSize": "[variables('linuxVmSize')]" 273 | }, 274 | "osProfile": { 275 | "computerName": "[variables('linuxPublicDnsName')]", 276 | "adminUsername": "[parameters('adminUsername')]", 277 | "adminPassword": "[parameters('adminPassword')]" 278 | }, 279 | "storageProfile": { 280 | "imageReference": { 281 | "publisher": "[variables('imagePublisher')]", 282 | "offer": "[variables('imageOffer')]", 283 | "sku": "[parameters('ubuntuOSVersion')]", 284 | "version": "latest" 285 | }, 286 | "osDisk": { 287 | "name": "LinuxOsdisk", 288 | "vhd": { 289 | "uri": "[concat('http://', variables('vhdStorageName'), '.blob.core.windows.net/', variables('vhdStorageContainerName'), '/', variables('OSDiskName'), '.vhd')]" 290 | }, 291 | "caching": "ReadWrite", 292 | "createOption": "FromImage" 293 | } 294 | }, 295 | "networkProfile": { 296 | "networkInterfaces": [ 297 | { 298 | "id": "[resourceId('Microsoft.Network/networkInterfaces', variables('linuxNicName'))]" 299 | } 300 | ] 301 | } 302 | } 303 | } 304 | ] 305 | } 306 | -------------------------------------------------------------------------------- /infra/AzureTestCellDeployment/1Windows_CellDeployment.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", 3 | "contentVersion": "1.0.0.0", 4 | "parameters": { 5 | "adminUsername": { 6 | "type": "string", 7 | "minLength": 1, 8 | "metadata": { 9 | "description": "User name for the Virtual Machine." 10 | } 11 | }, 12 | "adminPassword": { 13 | "type": "string", 14 | "metadata": { 15 | "description": "Password for the Virtual Machine." 16 | } 17 | }, 18 | "dnsNamePrefix": { 19 | "type": "string", 20 | "minLength": 1, 21 | "maxLength": 10, 22 | "metadata": { 23 | "description": "Globally unique DNS Name prefix to which the VM names will be appended for to access the Virtual Machine." 24 | } 25 | } 26 | }, 27 | "variables": { 28 | "networkSecurityGroupName": "networkSecurityGroup", 29 | "addressPrefix": "10.0.0.0/16", 30 | "subnetName": "Subnet", 31 | "subnetPrefix": "10.0.0.0/24", 32 | "vhdStorageType": "Standard_LRS", 33 | "publicIPAddressType": "Dynamic", 34 | "winPublicIPAddressName": "WinPublicIP", 35 | "winVmName": "Win1", 36 | "winPublicDnsName": "[toLower(concat(parameters('dnsNamePrefix'),'-',variables('winVmName')))]", 37 | "vhdStorageContainerName": "vhds", 38 | "virtualNetworkName": "VNET", 39 | "vnetId": "[resourceId('Microsoft.Network/virtualNetworks', variables('virtualNetworkName'))]", 40 | "subnetRef": "[concat(variables('vnetId'), '/subnets/', variables('subnetName'))]", 41 | "vhdStorageName": "[concat('vhdstorage', uniqueString(resourceGroup().id))]", 42 | "Win1ImagePublisher": "MicrosoftWindowsServer", 43 | "Win1ImageOffer": "WindowsServer", 44 | "Win1OSDiskName": "Win1OSDisk", 45 | "Win1WindowsOSVersion": "2012-R2-Datacenter", 46 | "Win1VmSize": "Standard_A2", 47 | "Win1SubnetRef": "[concat(variables('vnetId'), '/subnets/', variables('subnetName'))]", 48 | "Win1StorageAccountContainerName": "vhds", 49 | "Win1NicName": "Win1Nic", 50 | "hostDNSNameScriptArgument": "[concat('*.',resourceGroup().location,'.cloudapp.azure.com')]" 51 | }, 52 | "resources": [ 53 | { 54 | "type": "Microsoft.Storage/storageAccounts", 55 | "name": "[variables('vhdStorageName')]", 56 | "apiVersion": "2015-06-15", 57 | "location": "[resourceGroup().location]", 58 | "tags": { 59 | "displayName": "StorageAccount" 60 | }, 61 | "properties": { 62 | "accountType": "[variables('vhdStorageType')]" 63 | } 64 | }, 65 | { 66 | "apiVersion": "2015-06-15", 67 | "type": "Microsoft.Network/publicIPAddresses", 68 | "name": "[variables('winPublicIPAddressName')]", 69 | "location": "[resourceGroup().location]", 70 | "tags": { 71 | "displayName": "WinPublicIPAddress" 72 | }, 73 | "properties": { 74 | "publicIPAllocationMethod": "[variables('publicIPAddressType')]", 75 | "dnsSettings": { 76 | "domainNameLabel": "[variables('winPublicDnsName')]" 77 | } 78 | } 79 | }, 80 | { 81 | "type": "Microsoft.Network/networkSecurityGroups", 82 | "name": "[variables('networkSecurityGroupName')]", 83 | "apiVersion": "2015-06-15", 84 | "location": "[resourceGroup().location]", 85 | "tags": { 86 | "displayName": "NetworkSecurityGroup" 87 | }, 88 | "properties": { 89 | "securityRules": [ 90 | { 91 | "name": "allow_rdp", 92 | "properties": { 93 | "description": "Allow RDP", 94 | "protocol": "Tcp", 95 | "sourcePortRange": "*", 96 | "destinationPortRange": "3389", 97 | "sourceAddressPrefix": "Internet", 98 | "destinationAddressPrefix": "*", 99 | "access": "Allow", 100 | "priority": 100, 101 | "direction": "Inbound" 102 | } 103 | }, 104 | { 105 | "name": "allow_ssh", 106 | "properties": { 107 | "description": "Allow RDP", 108 | "protocol": "Tcp", 109 | "sourcePortRange": "*", 110 | "destinationPortRange": "22", 111 | "sourceAddressPrefix": "Internet", 112 | "destinationAddressPrefix": "*", 113 | "access": "Allow", 114 | "priority": 101, 115 | "direction": "Inbound" 116 | } 117 | }, 118 | 119 | { 120 | "name": "allow_remote_powershell_http", 121 | "properties": { 122 | "description": "Allow Remote Powershell HTTP", 123 | "protocol": "Tcp", 124 | "sourcePortRange": "*", 125 | "destinationPortRange": "5985", 126 | "sourceAddressPrefix": "Internet", 127 | "destinationAddressPrefix": "*", 128 | "access": "Allow", 129 | "priority": 102, 130 | "direction": "Inbound" 131 | } 132 | }, 133 | { 134 | "name": "allow_remote_powershell_https", 135 | "properties": { 136 | "description": "Allow Remote Powershell HTTPS", 137 | "protocol": "Tcp", 138 | "sourcePortRange": "*", 139 | "destinationPortRange": "5986", 140 | "sourceAddressPrefix": "Internet", 141 | "destinationAddressPrefix": "*", 142 | "access": "Allow", 143 | "priority": 103, 144 | "direction": "Inbound" 145 | } 146 | }, 147 | 148 | 149 | { 150 | "name": "allow_web", 151 | "properties": { 152 | "description": "Allow WEB", 153 | "protocol": "Tcp", 154 | "sourcePortRange": "*", 155 | "destinationPortRange": "80-85", 156 | "sourceAddressPrefix": "Internet", 157 | "destinationAddressPrefix": "*", 158 | "access": "Allow", 159 | "priority": 104, 160 | "direction": "Inbound" 161 | } 162 | }, 163 | { 164 | "name": "allow_web_proxy", 165 | "properties": { 166 | "description": "Allow WEB Proxy", 167 | "protocol": "Tcp", 168 | "sourcePortRange": "*", 169 | "destinationPortRange": "8080", 170 | "sourceAddressPrefix": "Internet", 171 | "destinationAddressPrefix": "*", 172 | "access": "Allow", 173 | "priority": 105, 174 | "direction": "Inbound" 175 | } 176 | }, 177 | { 178 | "name": "allow_kestrel", 179 | "properties": { 180 | "description": "Allow Kestrel", 181 | "protocol": "Tcp", 182 | "sourcePortRange": "*", 183 | "destinationPortRange": "5000-5010", 184 | "sourceAddressPrefix": "Internet", 185 | "destinationAddressPrefix": "*", 186 | "access": "Allow", 187 | "priority": 106, 188 | "direction": "Inbound" 189 | } 190 | } 191 | ] 192 | } 193 | }, 194 | { 195 | "apiVersion": "2015-06-15", 196 | "type": "Microsoft.Network/virtualNetworks", 197 | "name": "[variables('virtualNetworkName')]", 198 | "location": "[resourceGroup().location]", 199 | "tags": { 200 | "displayName": "VirtualNetwork" 201 | }, 202 | "dependsOn": [ 203 | "[concat('Microsoft.Network/networkSecurityGroups/', variables('networkSecurityGroupName'))]" 204 | ], 205 | "properties": { 206 | "addressSpace": { 207 | "addressPrefixes": [ 208 | "[variables('addressPrefix')]" 209 | ] 210 | }, 211 | "subnets": [ 212 | { 213 | "name": "[variables('subnetName')]", 214 | "properties": { 215 | "addressPrefix": "[variables('subnetPrefix')]", 216 | "networkSecurityGroup": { 217 | "id": "[resourceId('Microsoft.Network/networkSecurityGroups', variables('networkSecurityGroupName'))]" 218 | } 219 | } 220 | } 221 | ] 222 | } 223 | }, 224 | { 225 | "name": "[variables('Win1NicName')]", 226 | "type": "Microsoft.Network/networkInterfaces", 227 | "location": "[resourceGroup().location]", 228 | "apiVersion": "2015-06-15", 229 | "dependsOn": [ 230 | "[concat('Microsoft.Network/publicIPAddresses/', variables('winPublicIPAddressName'))]", 231 | "[concat('Microsoft.Network/virtualNetworks/', variables('virtualNetworkName'))]" 232 | ], 233 | "tags": { 234 | "displayName": "Win1Nic" 235 | }, 236 | "properties": { 237 | "ipConfigurations": [ 238 | { 239 | "name": "ipconfig1", 240 | "properties": { 241 | "privateIPAllocationMethod": "Dynamic", 242 | "publicIPAddress": { 243 | "id": "[resourceId('Microsoft.Network/publicIPAddresses', variables('winPublicIPAddressName'))]" 244 | }, 245 | "subnet": { 246 | "id": "[variables('Win1SubnetRef')]" 247 | } 248 | } 249 | } 250 | ] 251 | } 252 | }, 253 | { 254 | "name": "[variables('winVmName')]", 255 | "type": "Microsoft.Compute/virtualMachines", 256 | "location": "[resourceGroup().location]", 257 | "apiVersion": "2015-06-15", 258 | "dependsOn": [ 259 | "[concat('Microsoft.Storage/storageAccounts/', variables('vhdStorageName'))]", 260 | "[concat('Microsoft.Network/networkInterfaces/', variables('Win1NicName'))]" 261 | ], 262 | "tags": { 263 | "displayName": "WinVM" 264 | }, 265 | "properties": { 266 | "hardwareProfile": { 267 | "vmSize": "[variables('Win1VmSize')]" 268 | }, 269 | "osProfile": { 270 | "computerName": "[variables('winPublicDnsName')]", 271 | "adminUsername": "[parameters('adminUsername')]", 272 | "adminPassword": "[parameters('adminPassword')]" 273 | }, 274 | "storageProfile": { 275 | "imageReference": { 276 | "publisher": "[variables('Win1ImagePublisher')]", 277 | "offer": "[variables('Win1ImageOffer')]", 278 | "sku": "[variables('Win1WindowsOSVersion')]", 279 | "version": "latest" 280 | }, 281 | "osDisk": { 282 | "name": "Win1OSDisk", 283 | "vhd": { 284 | "uri": "[concat('http://', variables('vhdStorageName'), '.blob.core.windows.net/', variables('Win1StorageAccountContainerName'), '/', variables('Win1OSDiskName'), '.vhd')]" 285 | }, 286 | "caching": "ReadWrite", 287 | "createOption": "FromImage" 288 | } 289 | }, 290 | "networkProfile": { 291 | "networkInterfaces": [ 292 | { 293 | "id": "[resourceId('Microsoft.Network/networkInterfaces', variables('Win1NicName'))]" 294 | } 295 | ] 296 | } 297 | }, 298 | 299 | "resources": [ 300 | { 301 | "apiVersion": "2015-06-15", 302 | "type": "Microsoft.Compute/virtualMachines/extensions", 303 | "name": "[concat(variables('winVmName'),'/WinRMCustomScriptExtension')]", 304 | "location": "[resourceGroup().location]", 305 | "dependsOn": [ 306 | "[concat('Microsoft.Compute/virtualMachines/', variables('winVmName'))]" 307 | ], 308 | "properties": { 309 | "publisher": "Microsoft.Compute", 310 | "type": "CustomScriptExtension", 311 | "typeHandlerVersion": "1.4", 312 | "settings": { 313 | "fileUris": [ 314 | "https://raw.githubusercontent.com/Azure/azure-quickstart-templates/master/201-vm-winrm-windows/ConfigureWinRM.ps1", 315 | "https://raw.githubusercontent.com/Azure/azure-quickstart-templates/master/201-vm-winrm-windows/makecert.exe", 316 | "https://raw.githubusercontent.com/Azure/azure-quickstart-templates/master/201-vm-winrm-windows/winrmconf.cmd" 317 | ], 318 | "commandToExecute": "[concat('powershell -ExecutionPolicy Unrestricted -file ConfigureWinRM.ps1 ',variables('hostDNSNameScriptArgument'))]" 319 | } 320 | } 321 | } 322 | ] 323 | 324 | 325 | } 326 | ] 327 | } 328 | -------------------------------------------------------------------------------- /infra/AzureTestCellDeployment/Deploy-1Linux-TestCell.ps1: -------------------------------------------------------------------------------- 1 | #Requires -Version 3.0 2 | #Requires -Module AzureRM.Resources 3 | #Requires -Module Azure.Storage 4 | 5 | Param( 6 | [string] [Parameter(Mandatory=$true)] $SubscriptionId, 7 | [string] [Parameter(Mandatory=$true)] $ResourceGroupLocation, 8 | [string] [Parameter(Mandatory=$true)] $ResourceGroupName, 9 | [string] [Parameter(Mandatory=$true)] $AdminPassword, 10 | [string] [Parameter(Mandatory=$true)] $MqttBroker, 11 | [string] [Parameter(Mandatory=$true)] $MqttUser, 12 | [string] [Parameter(Mandatory=$true)] $MqttPassword 13 | ) 14 | 15 | $res = cmd /c where plink.exe 16 | if (-not $res) 17 | { 18 | Write-Host "Error: To configure Linux VMs, the plink.exe ssh utility must be in the path. " 19 | return 20 | } 21 | 22 | $TemplateFile = "1Linux_CellDeployment.json" 23 | $TemplateFile = [System.IO.Path]::Combine($PSScriptRoot, $TemplateFile) 24 | 25 | $currentSubscription = (Get-AzureRmContext).Subscription 26 | if ($currentSubscription.SubscriptionId -ne $SubscriptionId) 27 | { 28 | Write-Host "Setting current subscription" 29 | Select-AzureRmSubscription -SubscriptionID $SubscriptionId 30 | } 31 | 32 | # validate location 33 | $azureLocations = ((Get-AzureRmResourceProvider -ProviderNamespace Microsoft.Compute).ResourceTypes | Where-Object ResourceTypeName -eq virtualMachines).Locations 34 | if (-not $azureLocations.Contains($ResourceGroupLocation) ) 35 | { 36 | Write-Host "Error: Invalid location specified. The location '" $ResourceGroupLocation "' does not exist or does not support VMs" 37 | Write-Host "Valid locations are:" 38 | $azureLocations 39 | return 40 | } 41 | 42 | # Create or update the resource group using the specified template file and template parameters file 43 | New-AzureRmResourceGroup -Name $ResourceGroupName -Location $ResourceGroupLocation -Verbose -Force -ErrorAction Stop 44 | 45 | New-AzureRmResourceGroupDeployment -Name ((Get-ChildItem $TemplateFile).BaseName + '-' + ((Get-Date).ToUniversalTime()).ToString('MMdd-HHmm')) ` 46 | -ResourceGroupName $ResourceGroupName ` 47 | -TemplateFile $TemplateFile ` 48 | -dnsNamePrefix $ResourceGroupName ` 49 | -adminUserName "asplab" ` 50 | -adminPassword $AdminPassword ` 51 | -Force -Verbose 52 | 53 | 54 | #deploy agent on Linux VM 55 | Write-Host "Starting Setup For Linux VM" 56 | $linuxIpResource = Get-AzureRmResource -ResourceGroupName $ResourceGroupName -ResourceName "LinuxPublicIP" -ExpandProperties 57 | $linuxIP = $linuxIpResource.Properties.IpAddress 58 | Write-Host "Linux VM IP: "$linuxIP 59 | 60 | $tempScriptName = [System.IO.Path]::GetTempFileName() + ".ssh" 61 | $sshScriptContent = Get-Content "LinuxSetup.ssh" 62 | $sshScriptContent = $sshScriptContent -replace "", $MqttBroker 63 | $sshScriptContent = $sshScriptContent -replace "", $MqttUser 64 | $sshScriptContent = $sshScriptContent -replace "", $MqttPassword 65 | Set-Content $tempScriptName -Value $sshScriptContent 66 | 67 | Write-Host "Doing linux config using temporary file: " + $tempScriptName 68 | cmd /c echo "Y`r" | plink.exe $linuxIP -ssh -l "asplab" -pw $AdminPassword -m $tempScriptName 69 | Remove-Item $tempScriptName 70 | 71 | 72 | 73 | 74 | 75 | -------------------------------------------------------------------------------- /infra/AzureTestCellDeployment/Deploy-1Win-TestCell.ps1: -------------------------------------------------------------------------------- 1 | #Requires -Version 3.0 2 | #Requires -Module AzureRM.Resources 3 | #Requires -Module Azure.Storage 4 | 5 | Param( 6 | [string] [Parameter(Mandatory=$true)] $SubscriptionId, 7 | [string] [Parameter(Mandatory=$true)] $ResourceGroupLocation, 8 | [string] [Parameter(Mandatory=$true)] $ResourceGroupName, 9 | [string] [Parameter(Mandatory=$true)] $AdminPassword, 10 | [string] [Parameter(Mandatory=$true)] $MqttBroker, 11 | [string] [Parameter(Mandatory=$true)] $MqttUser, 12 | [string] [Parameter(Mandatory=$true)] $MqttPassword 13 | ) 14 | 15 | if ($ResourceGroupName.Length -gt 10) 16 | { 17 | Write-Host "Error: The ResourceGroupName parameter must be 10 chars or shorter. " 18 | Write-Host " This limitation is necessary because of windows machine name length limitations, " 19 | Write-Host " and the fact that the ResourceGroupName will become part of the windows machine name. " 20 | return 21 | } 22 | 23 | $TemplateFile = "1Windows_CellDeployment.json" 24 | $TemplateFile = [System.IO.Path]::Combine($PSScriptRoot, $TemplateFile) 25 | 26 | $currentSubscription = (Get-AzureRmContext).Subscription 27 | if ($currentSubscription.SubscriptionId -ne $SubscriptionId) 28 | { 29 | Write-Host "Setting current subscription" 30 | Select-AzureRmSubscription -SubscriptionID $SubscriptionId 31 | } 32 | 33 | # validate location 34 | $azureLocations = ((Get-AzureRmResourceProvider -ProviderNamespace Microsoft.Compute).ResourceTypes | Where-Object ResourceTypeName -eq virtualMachines).Locations 35 | if (-not $azureLocations.Contains($ResourceGroupLocation) ) 36 | { 37 | Write-Host "Error: Invalid location specified. The location '" $ResourceGroupLocation "' does not exist or does not support VMs" 38 | Write-Host "Valid locations are:" 39 | $azureLocations 40 | return 41 | } 42 | 43 | # Create or update the resource group using the specified template file and template parameters file 44 | New-AzureRmResourceGroup -Name $ResourceGroupName -Location $ResourceGroupLocation -Verbose -Force -ErrorAction Stop 45 | 46 | New-AzureRmResourceGroupDeployment -Name ((Get-ChildItem $TemplateFile).BaseName + '-' + ((Get-Date).ToUniversalTime()).ToString('MMdd-HHmm')) ` 47 | -ResourceGroupName $ResourceGroupName ` 48 | -TemplateFile $TemplateFile ` 49 | -dnsNamePrefix $ResourceGroupName ` 50 | -adminUserName "asplab" ` 51 | -adminPassword $AdminPassword ` 52 | -Force -Verbose 53 | 54 | #deploy agent on Windows VM 55 | $soptions = New-PSSessionOption -SkipCACheck 56 | $securePwd = ConvertTo-SecureString $AdminPassword -AsPlainText -Force 57 | $cred = new-object -typename System.Management.Automation.PSCredential -argumentlist "asplab", $securePwd 58 | 59 | Write-Host "Starting Setup For Windows VM" 60 | $WinIpResource1 = Get-AzureRmPublicIpAddress -ResourceGroupName $ResourceGroupName -Name WinPublicIP 61 | $WinFQDN1 = $WinIpResource1.DnsSettings.Fqdn 62 | Write-Host "Windows VM Domain Name: "$WinFQDN1 63 | 64 | $remotePsSession1 = New-PSSession -ComputerName $WinFQDN1 -Port 5986 -Credential $cred -SessionOption $soptions -UseSSL 65 | Write-Host "Starting Core Setup For Windows VM" 66 | Invoke-Command -Session $remotePsSession1 -FilePath WindowsVmSetup-Core.ps1 -ArgumentList "asplab",$AdminPassword,$MqttBroker,$MqttUser,$MqttPassword 67 | Remove-PSSession $remotePsSession1 68 | 69 | Restart-AzureRmVM -ResourceGroupName $ResourceGroupName -Name "Win1" 70 | Write-Host "Kicked off reboot of Windows VM" 71 | 72 | 73 | 74 | 75 | 76 | -------------------------------------------------------------------------------- /infra/AzureTestCellDeployment/Deploy-MultiRegion-TestCell.ps1: -------------------------------------------------------------------------------- 1 | Param( 2 | [string] [Parameter(Mandatory=$true)] $SubscriptionId, 3 | [string] [Parameter(Mandatory=$true)] $ResourceGroupName, 4 | [string[]] [Parameter(Mandatory=$true)] $LocationList, 5 | [string] [Parameter(Mandatory=$true)] $OsType, 6 | [string] [Parameter(Mandatory=$true)] $AdminPassword, 7 | [string] [Parameter(Mandatory=$true)] $MqttBroker, 8 | [string] [Parameter(Mandatory=$true)] $MqttUser, 9 | [string] [Parameter(Mandatory=$true)] $MqttPassword 10 | ) 11 | 12 | 13 | function DoWindowsDeployment() 14 | { 15 | Param( 16 | [string[]] [Parameter(Mandatory=$true)] $locations, 17 | [string[]] [Parameter(Mandatory=$true)] $locationNames 18 | ) 19 | 20 | if ($ResourceGroupName.Length -gt 7) #make max length contingent on trimmed max location length. 21 | { 22 | Write-Host "Error: The ResourceGroupName parameter must be 7 chars or shorter. " 23 | Write-Host " This limitation is necessary because of windows machine name length limitations, " 24 | Write-Host " and the fact that the ResourceGroupName will become part of the windows machine name. " 25 | return 26 | } 27 | 28 | $TemplateFile = "MultiRegion_Windows_CellDeployment.json" 29 | $TemplateFile = [System.IO.Path]::Combine($PSScriptRoot, $TemplateFile) 30 | 31 | # Create or update the resource group using the specified template file and template parameters file 32 | New-AzureRmResourceGroup -Name $ResourceGroupName -Location "Central US" -Verbose -Force -ErrorAction Stop 33 | 34 | New-AzureRmResourceGroupDeployment -Name ((Get-ChildItem $TemplateFile).BaseName + '-' + ((Get-Date).ToUniversalTime()).ToString('MMdd-HHmm')) ` 35 | -ResourceGroupName $ResourceGroupName ` 36 | -TemplateFile $TemplateFile ` 37 | -dnsNamePrefix $ResourceGroupName ` 38 | -adminUserName "asplab" ` 39 | -adminPassword $AdminPassword ` 40 | -locations $locations ` 41 | -locationNames $locationNames ` 42 | -Force -Verbose 43 | 44 | #Set up Windows VMs 45 | $FQDNs = @() 46 | $soptions = New-PSSessionOption -SkipCACheck 47 | $securePwd = ConvertTo-SecureString $AdminPassword -AsPlainText -Force 48 | $cred = new-object -typename System.Management.Automation.PSCredential -argumentlist "asplab", $securePwd 49 | 50 | for($i = 0; $i -lt $locations.Count; $i++) 51 | { 52 | $location = $locations[$i] 53 | Write-Host "Starting Setup for region: $location" 54 | 55 | $publicIpName = "WinPublicIP-" + $location 56 | $WinIpResource = Get-AzureRmPublicIpAddress -ResourceGroupName $ResourceGroupName -Name $publicIpName 57 | $WinFQDN = $WinIpResource.DnsSettings.Fqdn 58 | Write-Host "VM FQ Domain Name for region"$location": "$WinFQDN 59 | $FQDNs += $WinFQDN 60 | 61 | $remotePsSession = New-PSSession -ComputerName $WinFQDN -Port 5986 -Credential $cred -SessionOption $soptions -UseSSL 62 | Write-Host "Starting Core Setup" 63 | Invoke-Command -Session $remotePsSession -FilePath WindowsVmSetup-Core.ps1 -ArgumentList "asplab",$AdminPassword,$MqttBroker,$MqttUser,$MqttPassword 64 | Remove-PSSession $remotePsSession 65 | 66 | $vmName = "WinVM-$location-" + ($ResourceGroupName+$locationNames[$i]).ToLower() 67 | Restart-AzureRmVM -ResourceGroupName $ResourceGroupName -Name $vmName 68 | Write-Host "Kicked off reboot of Windows VM: $vmName" 69 | } 70 | 71 | Write-Host "" 72 | Write-Host "******************************************************************************************" 73 | Write-Host "Deployment summary" 74 | Write-Host "" 75 | Write-Host "" 76 | 77 | "{0,-22}{1,-20}{2}" -f "Region", "Host Name", "FQDN" 78 | Write-Host "" 79 | for($i = 0; $i -lt $LocationList.Count; $i++) 80 | { 81 | $hostName = $ResourceGroupName + $locationNames[$i] 82 | "{0,-22}{1,-20}{2}" -f $LocationList[$i], $hostName, $FQDNs[$i] 83 | } 84 | Write-Host "******************************************************************************************" 85 | } 86 | 87 | 88 | function DoLinuxDeployment() 89 | { 90 | Param( 91 | [string[]] [Parameter(Mandatory=$true)] $locations, 92 | [string[]] [Parameter(Mandatory=$true)] $locationNames 93 | ) 94 | 95 | $res = cmd /c where plink.exe 96 | if (-not $res) 97 | { 98 | Write-Host "Error: To configure Linux VMs, the plink.exe ssh utility must be in the path. " 99 | return 100 | } 101 | 102 | $TemplateFile = "MultiRegion_Linux_CellDeployment.json" 103 | $TemplateFile = [System.IO.Path]::Combine($PSScriptRoot, $TemplateFile) 104 | 105 | # Create or update the resource group using the specified template file and template parameters file 106 | New-AzureRmResourceGroup -Name $ResourceGroupName -Location "Central US" -Verbose -Force -ErrorAction Stop 107 | 108 | New-AzureRmResourceGroupDeployment -Name ((Get-ChildItem $TemplateFile).BaseName + '-' + ((Get-Date).ToUniversalTime()).ToString('MMdd-HHmm')) ` 109 | -ResourceGroupName $ResourceGroupName ` 110 | -TemplateFile $TemplateFile ` 111 | -dnsNamePrefix $ResourceGroupName ` 112 | -adminUserName "asplab" ` 113 | -adminPassword $AdminPassword ` 114 | -locations $locations ` 115 | -locationNames $locationNames ` 116 | -Force -Verbose 117 | 118 | #Set up Linux VMs 119 | $IPAddresses = @() 120 | 121 | for($i = 0; $i -lt $locations.Count; $i++) 122 | { 123 | $location = $locations[$i] 124 | Write-Host "Starting Setup for region: $location" 125 | 126 | $publicIpName = "LinuxPublicIP-" + $location 127 | $linuxIpResource = Get-AzureRmResource -ResourceGroupName $ResourceGroupName -ResourceName $publicIpName -ExpandProperties 128 | $linuxIP = $linuxIpResource.Properties.IpAddress 129 | Write-Host "Linux VM IP: "$linuxIP 130 | $IPAddresses += $linuxIP 131 | 132 | $tempScriptName = [System.IO.Path]::GetTempFileName() + ".ssh" 133 | $sshScriptContent = Get-Content "LinuxSetup.ssh" 134 | $sshScriptContent = $sshScriptContent -replace "", $MqttBroker 135 | $sshScriptContent = $sshScriptContent -replace "", $MqttUser 136 | $sshScriptContent = $sshScriptContent -replace "", $MqttPassword 137 | Set-Content $tempScriptName -Value $sshScriptContent 138 | 139 | Write-Host "Doing linux config using temporary file: " $tempScriptName 140 | cmd /c echo "Y`r" | plink.exe $linuxIP -ssh -l "asplab" -pw $AdminPassword -m $tempScriptName 141 | 142 | Remove-Item $tempScriptName 143 | } 144 | 145 | Write-Host "" 146 | Write-Host "******************************************************************************************" 147 | Write-Host "Deployment summary" 148 | Write-Host "" 149 | Write-Host "" 150 | 151 | "{0,-22}{1,-20}{2}" -f "Region", "Host Name", "IP" 152 | Write-Host "" 153 | for($i = 0; $i -lt $LocationList.Count; $i++) 154 | { 155 | $hostName = $ResourceGroupName + $locationNames[$i] 156 | "{0,-22}{1,-20}{2}" -f $LocationList[$i], $hostName, $IPAddresses[$i] 157 | } 158 | Write-Host "******************************************************************************************" 159 | } 160 | 161 | 162 | 163 | function GetLocationNameArray 164 | { 165 | Param( 166 | [string[]] [Parameter(Mandatory=$true)] $Locations 167 | ) 168 | 169 | $nameArray = @() 170 | for($i = 0; $i -lt $Locations.Count; $i++) 171 | { 172 | $loc = $Locations[$i] 173 | 174 | $trimmedLoc = $loc -replace " ", "" 175 | if ($trimmedLoc.Length -le 8) 176 | { 177 | $nameArray += $trimmedLoc 178 | } 179 | else 180 | { 181 | $tokens = -split $loc 182 | $nameArray += "" 183 | foreach ($token in $tokens) 184 | { 185 | if ($token.Length -le 3) 186 | { 187 | $nameArray[$i] = $nameArray[$i] + $token 188 | } 189 | else 190 | { 191 | if ($tokens.Count -le 2) 192 | { 193 | $nameArray[$i] = $nameArray[$i] + $token.Substring(0,4) 194 | } 195 | else 196 | { 197 | $nameArray[$i] = $nameArray[$i] + $token.Substring(0,3) 198 | } 199 | } 200 | } 201 | if ($nameArray[$i].Length -gt 8) 202 | { 203 | $nameArray[$i] = $nameArray[$i].Substring(0,8) 204 | } 205 | } 206 | 207 | } 208 | return $nameArray 209 | } 210 | 211 | 212 | 213 | 214 | $currentSubscription = (Get-AzureRmContext).Subscription 215 | if ($currentSubscription.SubscriptionId -ne $SubscriptionId) 216 | { 217 | Write-Host "Setting current subscription" 218 | Select-AzureRmSubscription -SubscriptionID $SubscriptionId 219 | } 220 | 221 | # validate passed in locations 222 | $azureLocations = ((Get-AzureRmResourceProvider -ProviderNamespace Microsoft.Compute).ResourceTypes | Where-Object ResourceTypeName -eq virtualMachines).Locations 223 | foreach ($loc in $LocationList) 224 | { 225 | if (-not $azureLocations.Contains($loc) ) 226 | { 227 | Write-Host "Error: Invalid location specified. The location '" $loc "' does not exist or does not support VMs" 228 | Write-Host "Valid locations are:" 229 | $azureLocations 230 | return 231 | } 232 | } 233 | 234 | # create short location acryonyms and remove white space from location list 235 | $locationNames = GetLocationNameArray($LocationList) 236 | $locations = @() 237 | for($i = 0; $i -lt $LocationList.Count; $i++) 238 | { 239 | $locations += $LocationList[$i] -replace " ","" 240 | } 241 | 242 | if ($OsType.ToLower() -eq "windows") 243 | { 244 | DoWindowsDeployment -locations $locations -locationNames $locationNames 245 | } 246 | elseif ($OsType.ToLower() -eq "linux") 247 | { 248 | DoLinuxDeployment -locations $locations -locationNames $locationNames 249 | } 250 | else 251 | { 252 | Write-Host "Error: Please specify 'Windows' or 'Linux' for OsType" 253 | } 254 | -------------------------------------------------------------------------------- /infra/AzureTestCellDeployment/Deploy-WinLinux-TestCell.ps1: -------------------------------------------------------------------------------- 1 | #Requires -Version 3.0 2 | #Requires -Module AzureRM.Resources 3 | #Requires -Module Azure.Storage 4 | 5 | Param( 6 | [string] [Parameter(Mandatory=$true)] $SubscriptionId, 7 | [string] [Parameter(Mandatory=$true)] $ResourceGroupLocation, 8 | [string] [Parameter(Mandatory=$true)] $ResourceGroupName, 9 | [string] [Parameter(Mandatory=$true)] $AdminPassword, 10 | [string] [Parameter(Mandatory=$true)] $MqttBroker, 11 | [string] [Parameter(Mandatory=$true)] $MqttUser, 12 | [string] [Parameter(Mandatory=$true)] $MqttPassword 13 | ) 14 | 15 | if ($ResourceGroupName.Length -gt 10) 16 | { 17 | Write-Host "Error: The ResourceGroupName parameter must be 10 chars or shorter. " 18 | Write-Host " This limitation is necessary because of windows machine name length limitations, " 19 | Write-Host " and the fact that the ResourceGroupName will become part of the windows machine name. " 20 | return 21 | } 22 | $res = cmd /c where plink.exe 23 | if (-not $res) 24 | { 25 | Write-Host "Error: To configure Linux VMs, the plink.exe ssh utility must be in the path. " 26 | return 27 | } 28 | 29 | 30 | $TemplateFile = "Windows-Linux_CellDeployment.json" 31 | $TemplateFile = [System.IO.Path]::Combine($PSScriptRoot, $TemplateFile) 32 | 33 | $currentSubscription = (Get-AzureRmContext).Subscription 34 | if ($currentSubscription.SubscriptionId -ne $SubscriptionId) 35 | { 36 | Write-Host "Setting current subscription" 37 | Select-AzureRmSubscription -SubscriptionID $SubscriptionId 38 | } 39 | 40 | # validate location 41 | $azureLocations = ((Get-AzureRmResourceProvider -ProviderNamespace Microsoft.Compute).ResourceTypes | Where-Object ResourceTypeName -eq virtualMachines).Locations 42 | if (-not $azureLocations.Contains($ResourceGroupLocation) ) 43 | { 44 | Write-Host "Error: Invalid location specified. The location '" $ResourceGroupLocation "' does not exist or does not support VMs" 45 | Write-Host "Valid locations are:" 46 | $azureLocations 47 | return 48 | } 49 | 50 | # Create or update the resource group using the specified template file and template parameters file 51 | New-AzureRmResourceGroup -Name $ResourceGroupName -Location $ResourceGroupLocation -Verbose -Force -ErrorAction Stop 52 | 53 | New-AzureRmResourceGroupDeployment -Name ((Get-ChildItem $TemplateFile).BaseName + '-' + ((Get-Date).ToUniversalTime()).ToString('MMdd-HHmm')) ` 54 | -ResourceGroupName $ResourceGroupName ` 55 | -TemplateFile $TemplateFile ` 56 | -dnsNamePrefix $ResourceGroupName ` 57 | -adminUserName "asplab" ` 58 | -adminPassword $AdminPassword ` 59 | -Force -Verbose 60 | 61 | Write-Host "Starting Setup For Windows VM" 62 | $soptions = New-PSSessionOption -SkipCACheck 63 | $securePwd = ConvertTo-SecureString $AdminPassword -AsPlainText -Force 64 | $cred = new-object -typename System.Management.Automation.PSCredential -argumentlist "asplab", $securePwd 65 | 66 | $WinIpResource1 = Get-AzureRmPublicIpAddress -ResourceGroupName $ResourceGroupName -Name WinPublicIP 67 | $WinFQDN1 = $WinIpResource1.DnsSettings.Fqdn 68 | Write-Host "Windows VM Domain Name: "$WinFQDN1 69 | 70 | $remotePsSession1 = New-PSSession -ComputerName $WinFQDN1 -Port 5986 -Credential $cred -SessionOption $soptions -UseSSL 71 | Write-Host "Starting Core Setup For Windows VM" 72 | Invoke-Command -Session $remotePsSession1 -FilePath WindowsVmSetup-Core.ps1 -ArgumentList "asplab",$AdminPassword,$MqttBroker,$MqttUser,$MqttPassword 73 | Remove-PSSession $remotePsSession1 74 | 75 | Restart-AzureRmVM -ResourceGroupName $ResourceGroupName -Name "Win1" 76 | Write-Host "Kicked off reboot of Windows VM" 77 | 78 | #deploy agent on Linux VM 79 | Write-Host "Starting Setup For Linux VM" 80 | $linuxIpResource = Get-AzureRmResource -ResourceGroupName $ResourceGroupName -ResourceName "LinuxPublicIP" -ExpandProperties 81 | $linuxIP = $linuxIpResource.Properties.IpAddress 82 | Write-Host "Linux VM IP: "$linuxIP 83 | 84 | $sshScriptContent = Get-Content "LinuxSetup.ssh" 85 | $tempScriptName = [System.IO.Path]::GetTempFileName() + ".ssh" 86 | $sshScriptContent = $sshScriptContent -replace "", $MqttBroker 87 | $sshScriptContent = $sshScriptContent -replace "", $MqttUser 88 | $sshScriptContent = $sshScriptContent -replace "", $MqttPassword 89 | Set-Content $tempScriptName -Value $sshScriptContent 90 | 91 | Write-Host "Doing linux config using temporary file: " + $tempScriptName 92 | cmd /c echo "Y`r" | plink.exe $linuxIP -ssh -l "asplab" -pw $AdminPassword -m $tempScriptName 93 | Remove-Item $tempScriptName 94 | -------------------------------------------------------------------------------- /infra/AzureTestCellDeployment/Deploy-WinWin-TestCell.ps1: -------------------------------------------------------------------------------- 1 | #Requires -Version 3.0 2 | #Requires -Module AzureRM.Resources 3 | #Requires -Module Azure.Storage 4 | 5 | Param( 6 | [string] [Parameter(Mandatory=$true)] $SubscriptionId, 7 | [string] [Parameter(Mandatory=$true)] $ResourceGroupLocation, 8 | [string] [Parameter(Mandatory=$true)] $ResourceGroupName, 9 | [string] [Parameter(Mandatory=$true)] $AdminPassword, 10 | [string] [Parameter(Mandatory=$true)] $MqttBroker, 11 | [string] [Parameter(Mandatory=$true)] $MqttUser, 12 | [string] [Parameter(Mandatory=$true)] $MqttPassword 13 | ) 14 | 15 | if ($ResourceGroupName.Length -gt 10) 16 | { 17 | Write-Host "Error: The ResourceGroupName parameter must be 10 chars or shorter. " 18 | Write-Host " This limitation is necessary because of windows machine name length limitations, " 19 | Write-Host " and the fact that the ResourceGroupName will become part of the windows machine name. " 20 | return 21 | } 22 | 23 | $TemplateFile = "Windows-Windows_CellDeployment.json" 24 | $TemplateFile = [System.IO.Path]::Combine($PSScriptRoot, $TemplateFile) 25 | 26 | $currentSubscription = (Get-AzureRmContext).Subscription 27 | if ($currentSubscription.SubscriptionId -ne $SubscriptionId) 28 | { 29 | Write-Host "Setting current subscription" 30 | Select-AzureRmSubscription -SubscriptionID $SubscriptionId 31 | } 32 | 33 | # validate location 34 | $azureLocations = ((Get-AzureRmResourceProvider -ProviderNamespace Microsoft.Compute).ResourceTypes | Where-Object ResourceTypeName -eq virtualMachines).Locations 35 | if (-not $azureLocations.Contains($ResourceGroupLocation) ) 36 | { 37 | Write-Host "Error: Invalid location specified. The location '" $ResourceGroupLocation "' does not exist or does not support VMs" 38 | Write-Host "Valid locations are:" 39 | $azureLocations 40 | return 41 | } 42 | 43 | # Create or update the resource group using the specified template file and template parameters file 44 | Write-Host "Creating Resource Group" 45 | New-AzureRmResourceGroup -Name $ResourceGroupName -Location $ResourceGroupLocation -Verbose -Force -ErrorAction Stop 46 | 47 | Write-Host "Starting template based deployment" 48 | New-AzureRmResourceGroupDeployment -Name ((Get-ChildItem $TemplateFile).BaseName + '-' + ((Get-Date).ToUniversalTime()).ToString('MMdd-HHmm')) ` 49 | -ResourceGroupName $ResourceGroupName ` 50 | -TemplateFile $TemplateFile ` 51 | -dnsNamePrefix $ResourceGroupName ` 52 | -adminUserName "asplab" ` 53 | -adminPassword $AdminPassword ` 54 | -Force -Verbose 55 | 56 | $soptions = New-PSSessionOption -SkipCACheck 57 | $securePwd = ConvertTo-SecureString $AdminPassword -AsPlainText -Force 58 | $cred = new-object -typename System.Management.Automation.PSCredential -argumentlist "asplab", $securePwd 59 | 60 | #Do setup on Windows VM1 61 | Write-Host "Starting Setup For Windows VM1" 62 | $WinIpResource1 = Get-AzureRmPublicIpAddress -ResourceGroupName $ResourceGroupName -Name Win1PublicIP 63 | $WinFQDN1 = $WinIpResource1.DnsSettings.Fqdn 64 | Write-Host "Windows VM1 Domain Name: "$WinFQDN1 65 | 66 | $remotePsSession1 = New-PSSession -ComputerName $WinFQDN1 -Port 5986 -Credential $cred -SessionOption $soptions -UseSSL 67 | Write-Host "Starting Core Setup For Windows VM1" 68 | Invoke-Command -Session $remotePsSession1 -FilePath WindowsVmSetup-Core.ps1 -ArgumentList "asplab",$AdminPassword,$MqttBroker,$MqttUser,$MqttPassword 69 | Remove-PSSession $remotePsSession1 70 | 71 | Restart-AzureRmVM -ResourceGroupName $ResourceGroupName -Name "Win1" 72 | Write-Host "Kicked off reboot of Windows VM1" 73 | 74 | #Do setup on Windows VM2 75 | Write-Host "Starting Setup For Windows VM2" 76 | $WinIpResource2 = Get-AzureRmPublicIpAddress -ResourceGroupName $ResourceGroupName -Name Win2PublicIP 77 | $WinFQDN2 = $WinIpResource2.DnsSettings.Fqdn 78 | Write-Host "Windows VM2 Domain Name: "$WinFQDN2 79 | 80 | $remotePsSession2 = New-PSSession -ComputerName $WinFQDN2 -Port 5986 -Credential $cred -SessionOption $soptions -UseSSL 81 | Write-Host "Starting Core Setup For Windows VM2" 82 | Invoke-Command -Session $remotePsSession2 -FilePath WindowsVmSetup-Core.ps1 -ArgumentList "asplab",$AdminPassword,$MqttBroker,$MqttUser,$MqttPassword 83 | Remove-PSSession $remotePsSession2 84 | 85 | Restart-AzureRmVM -ResourceGroupName $ResourceGroupName -Name "Win2" 86 | Write-Host "Kicked off reboot of Windows VM2" 87 | 88 | 89 | 90 | -------------------------------------------------------------------------------- /infra/AzureTestCellDeployment/LinuxSetup.ssh: -------------------------------------------------------------------------------- 1 | sudo apt-get -y install git 2 | git clone https://github.com/aspnet/Wave.git 3 | sudo ./Wave/scripts/install.sh 4 | -------------------------------------------------------------------------------- /infra/AzureTestCellDeployment/MultiRegion_Linux_CellDeployment.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", 3 | "contentVersion": "1.0.0.0", 4 | "parameters": { 5 | "adminUsername": { 6 | "type": "string", 7 | "minLength": 1, 8 | "metadata": { 9 | "description": "User name for the Virtual Machine." 10 | } 11 | }, 12 | "adminPassword": { 13 | "type": "string", 14 | "metadata": { 15 | "description": "Password for the Virtual Machine." 16 | } 17 | }, 18 | "dnsNamePrefix": { 19 | "type": "string", 20 | "minLength": 1, 21 | "maxLength": 10, 22 | "metadata": { 23 | "description": "Globally unique DNS Name prefix to which the VM names will be prepended for to access the Virtual Machine." 24 | } 25 | }, 26 | "locations": { 27 | "type": "array", 28 | "minLength": 1, 29 | "metadata": { 30 | "description": "An array of locations to which VM will be deployed" 31 | } 32 | }, 33 | "locationNames": { 34 | "type": "array", 35 | "minLength": 1, 36 | "metadata": { 37 | "description": "An array of location names that will be appended to the VM and other object names" 38 | } 39 | }, 40 | "ubuntuOSVersion": { 41 | "type": "string", 42 | "defaultValue": "14.04.4-LTS", 43 | "metadata": { 44 | "description": "The Ubuntu version for the VM. This will pick a fully patched image of this given Ubuntu version." 45 | } 46 | } 47 | }, 48 | "variables": { 49 | "locationCount": "[length(parameters('locations'))]", 50 | "networkSecurityGroupPrefix": "networkSecurityGroup-", 51 | "imagePublisher": "Canonical", 52 | "imageOffer": "UbuntuServer", 53 | "OSDiskName": "osdiskforlinux", 54 | "addressPrefix": "10.0.0.0/16", 55 | "subnetName": "Subnet", 56 | "subnetPrefix": "10.0.0.0/24", 57 | "vhdStorageType": "Standard_LRS", 58 | "publicIPAddressType": "Dynamic", 59 | "linuxPublicIPAddressPrefix": "LinuxPublicIP-", 60 | "vhdStorageContainerName": "vhds", 61 | "virtualNetworkNamePrefix": "VNET-", 62 | "vhdStorageNamePrefix": "[uniqueString(resourceGroup().id)]", 63 | "VmSize": "Standard_A2", 64 | "StorageAccountContainerName": "vhds", 65 | "NicNamePrefix": "Nic-", 66 | "vmNamePrefix": "LinuxVM-" 67 | }, 68 | "resources": [ 69 | { 70 | "type": "Microsoft.Storage/storageAccounts", 71 | "name": "[toLower(concat(variables('vhdStorageNamePrefix'),parameters('locationNames')[copyIndex()]))]", 72 | "apiVersion": "2015-06-15", 73 | "location": "[parameters('locations')[copyIndex()]]", 74 | "tags": { 75 | "displayName": "[toLower(concat(variables('vhdStorageNamePrefix'),parameters('dnsNamePrefix'),parameters('locationNames')[copyIndex()]))]" 76 | }, 77 | "properties": { 78 | "accountType": "[variables('vhdStorageType')]" 79 | }, 80 | "copy": { 81 | "name": "storageCopy", 82 | "count": "[variables('locationCount')]" 83 | } 84 | }, 85 | { 86 | "apiVersion": "2015-06-15", 87 | "type": "Microsoft.Network/publicIPAddresses", 88 | "name": "[concat(variables('linuxPublicIPAddressPrefix'),parameters('locations')[copyIndex()])]", 89 | "location": "[parameters('locations')[copyIndex()]]", 90 | "tags": { 91 | "displayName": "[concat(variables('linuxPublicIPAddressPrefix'),parameters('locations')[copyIndex()])]" 92 | }, 93 | "properties": { 94 | "publicIPAllocationMethod": "[variables('publicIPAddressType')]", 95 | "dnsSettings": { 96 | "domainNameLabel": "[toLower(concat(parameters('dnsNamePrefix'),parameters('locationNames')[copyIndex()]))]" 97 | } 98 | }, 99 | "copy": { 100 | "name": "publicIPAddressCopy", 101 | "count": "[variables('locationCount')]" 102 | } 103 | }, 104 | { 105 | "type": "Microsoft.Network/networkSecurityGroups", 106 | "name": "[concat(variables('networkSecurityGroupPrefix'),parameters('locations')[copyIndex()])]", 107 | "apiVersion": "2015-06-15", 108 | "location": "[parameters('locations')[copyIndex()]]", 109 | "tags": { 110 | "displayName": "[concat(variables('networkSecurityGroupPrefix'),parameters('locations')[copyIndex()])]" 111 | }, 112 | "properties": { 113 | "securityRules": [ 114 | { 115 | "name": "allow_rdp", 116 | "properties": { 117 | "description": "Allow RDP", 118 | "protocol": "Tcp", 119 | "sourcePortRange": "*", 120 | "destinationPortRange": "3389", 121 | "sourceAddressPrefix": "Internet", 122 | "destinationAddressPrefix": "*", 123 | "access": "Allow", 124 | "priority": 100, 125 | "direction": "Inbound" 126 | } 127 | }, 128 | { 129 | "name": "allow_ssh", 130 | "properties": { 131 | "description": "Allow RDP", 132 | "protocol": "Tcp", 133 | "sourcePortRange": "*", 134 | "destinationPortRange": "22", 135 | "sourceAddressPrefix": "Internet", 136 | "destinationAddressPrefix": "*", 137 | "access": "Allow", 138 | "priority": 101, 139 | "direction": "Inbound" 140 | } 141 | }, 142 | 143 | { 144 | "name": "allow_remote_powershell_http", 145 | "properties": { 146 | "description": "Allow Remote Powershell HTTP", 147 | "protocol": "Tcp", 148 | "sourcePortRange": "*", 149 | "destinationPortRange": "5985", 150 | "sourceAddressPrefix": "Internet", 151 | "destinationAddressPrefix": "*", 152 | "access": "Allow", 153 | "priority": 102, 154 | "direction": "Inbound" 155 | } 156 | }, 157 | { 158 | "name": "allow_remote_powershell_https", 159 | "properties": { 160 | "description": "Allow Remote Powershell HTTPS", 161 | "protocol": "Tcp", 162 | "sourcePortRange": "*", 163 | "destinationPortRange": "5986", 164 | "sourceAddressPrefix": "Internet", 165 | "destinationAddressPrefix": "*", 166 | "access": "Allow", 167 | "priority": 103, 168 | "direction": "Inbound" 169 | } 170 | }, 171 | 172 | 173 | { 174 | "name": "allow_web", 175 | "properties": { 176 | "description": "Allow WEB", 177 | "protocol": "Tcp", 178 | "sourcePortRange": "*", 179 | "destinationPortRange": "80-85", 180 | "sourceAddressPrefix": "Internet", 181 | "destinationAddressPrefix": "*", 182 | "access": "Allow", 183 | "priority": 104, 184 | "direction": "Inbound" 185 | } 186 | }, 187 | { 188 | "name": "allow_web_proxy", 189 | "properties": { 190 | "description": "Allow WEB Proxy", 191 | "protocol": "Tcp", 192 | "sourcePortRange": "*", 193 | "destinationPortRange": "8080", 194 | "sourceAddressPrefix": "Internet", 195 | "destinationAddressPrefix": "*", 196 | "access": "Allow", 197 | "priority": 105, 198 | "direction": "Inbound" 199 | } 200 | }, 201 | { 202 | "name": "allow_kestrel", 203 | "properties": { 204 | "description": "Allow Kestrel", 205 | "protocol": "Tcp", 206 | "sourcePortRange": "*", 207 | "destinationPortRange": "5000-5010", 208 | "sourceAddressPrefix": "Internet", 209 | "destinationAddressPrefix": "*", 210 | "access": "Allow", 211 | "priority": 106, 212 | "direction": "Inbound" 213 | } 214 | } 215 | ] 216 | }, 217 | "copy": { 218 | "name": "networkSecurityGroupCopy", 219 | "count": "[variables('locationCount')]" 220 | } 221 | }, 222 | { 223 | "apiVersion": "2015-06-15", 224 | "type": "Microsoft.Network/virtualNetworks", 225 | "name": "[concat(variables('virtualNetworkNamePrefix'),parameters('locations')[copyIndex()])]", 226 | "location": "[parameters('locations')[copyIndex()]]", 227 | "tags": { 228 | "displayName": "[concat(variables('virtualNetworkNamePrefix'),parameters('locations')[copyIndex()])]" 229 | }, 230 | "dependsOn": [ 231 | "networkSecurityGroupCopy" 232 | ], 233 | "properties": { 234 | "addressSpace": { 235 | "addressPrefixes": [ 236 | "[variables('addressPrefix')]" 237 | ] 238 | }, 239 | "subnets": [ 240 | { 241 | "name": "[variables('subnetName')]", 242 | "properties": { 243 | "addressPrefix": "[variables('subnetPrefix')]", 244 | "networkSecurityGroup": { 245 | "id": "[resourceId('Microsoft.Network/networkSecurityGroups', concat(variables('networkSecurityGroupPrefix'),parameters('locations')[copyIndex()]))]" 246 | } 247 | } 248 | } 249 | ] 250 | }, 251 | "copy": { 252 | "name": "virtualNetworksCopy", 253 | "count": "[variables('locationCount')]" 254 | } 255 | }, 256 | { 257 | "name": "[concat(variables('NicNamePrefix'),parameters('locations')[copyIndex()])]", 258 | "type": "Microsoft.Network/networkInterfaces", 259 | "location": "[parameters('locations')[copyIndex()]]", 260 | "apiVersion": "2015-06-15", 261 | "dependsOn": [ 262 | "publicIPAddressCopy", 263 | "virtualNetworksCopy" 264 | ], 265 | "tags": { 266 | "displayName": "[concat(variables('NicNamePrefix'),parameters('locations')[copyIndex()])]" 267 | }, 268 | "properties": { 269 | "ipConfigurations": [ 270 | { 271 | "name": "ipconfig1", 272 | "properties": { 273 | "privateIPAllocationMethod": "Dynamic", 274 | "publicIPAddress": { 275 | "id": "[resourceId('Microsoft.Network/publicIPAddresses', concat(variables('linuxPublicIPAddressPrefix'),parameters('locations')[copyIndex()]))]" 276 | }, 277 | "subnet": { 278 | "id": "[concat(resourceId('Microsoft.Network/virtualNetworks', concat(variables('virtualNetworkNamePrefix'),parameters('locations')[copyIndex()])), '/subnets/', variables('subnetName'))]" 279 | } 280 | } 281 | } 282 | ] 283 | }, 284 | "copy": { 285 | "name": "networkInterfacesCopy", 286 | "count": "[variables('locationCount')]" 287 | } 288 | }, 289 | 290 | 291 | { 292 | "name": "[concat(variables('vmNamePrefix'),parameters('locations')[copyIndex()],'-',toLower(parameters('dnsNamePrefix')),toLower(parameters('locationNames')[copyIndex()]))]", 293 | "apiVersion": "2015-06-15", 294 | "type": "Microsoft.Compute/virtualMachines", 295 | "location": "[parameters('locations')[copyIndex()]]", 296 | "tags": { 297 | "displayName": "[concat(variables('vmNamePrefix'),parameters('locations')[copyIndex()],'-',parameters('dnsNamePrefix'),parameters('locationNames')[copyIndex()])]" 298 | }, 299 | "dependsOn": [ 300 | "storageCopy", 301 | "networkInterfacesCopy" 302 | ], 303 | "properties": { 304 | "hardwareProfile": { 305 | "vmSize": "[variables('vmSize')]" 306 | }, 307 | "osProfile": { 308 | "computerName": "[concat(parameters('dnsNamePrefix'),parameters('locationNames')[copyIndex()])]", 309 | "adminUsername": "[parameters('adminUsername')]", 310 | "adminPassword": "[parameters('adminPassword')]" 311 | }, 312 | "storageProfile": { 313 | "imageReference": { 314 | "publisher": "[variables('imagePublisher')]", 315 | "offer": "[variables('imageOffer')]", 316 | "sku": "[parameters('ubuntuOSVersion')]", 317 | "version": "latest" 318 | }, 319 | "osDisk": { 320 | "name": "LinuxOsdisk", 321 | "vhd": { 322 | "uri": "[concat('http://', toLower(concat(variables('vhdStorageNamePrefix'),parameters('locationNames')[copyIndex()])), '.blob.core.windows.net/', variables('StorageAccountContainerName'), '/', variables('OSDiskName'), '.vhd')]" 323 | }, 324 | "caching": "ReadWrite", 325 | "createOption": "FromImage" 326 | } 327 | }, 328 | "networkProfile": { 329 | "networkInterfaces": [ 330 | { 331 | "id": "[resourceId('Microsoft.Network/networkInterfaces', concat(variables('NicNamePrefix'),parameters('locations')[copyIndex()]))]" 332 | } 333 | ] 334 | } 335 | }, 336 | "copy": { 337 | "name": "virtualMachineCopy", 338 | "count": "[variables('locationCount')]" 339 | } 340 | } 341 | ] 342 | } 343 | -------------------------------------------------------------------------------- /infra/AzureTestCellDeployment/MultiRegion_Windows_CellDeployment.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", 3 | "contentVersion": "1.0.0.0", 4 | "parameters": { 5 | "adminUsername": { 6 | "type": "string", 7 | "minLength": 1, 8 | "metadata": { 9 | "description": "User name for the Virtual Machine." 10 | } 11 | }, 12 | "adminPassword": { 13 | "type": "string", 14 | "metadata": { 15 | "description": "Password for the Virtual Machine." 16 | } 17 | }, 18 | "dnsNamePrefix": { 19 | "type": "string", 20 | "minLength": 1, 21 | "maxLength": 10, 22 | "metadata": { 23 | "description": "Globally unique DNS Name prefix to which the VM names will be prepended for to access the Virtual Machine." 24 | } 25 | }, 26 | "locations": { 27 | "type": "array", 28 | "minLength": 1, 29 | "metadata": { 30 | "description": "An array of locations to which VM will be deployed" 31 | } 32 | }, 33 | "locationNames": { 34 | "type": "array", 35 | "minLength": 1, 36 | "metadata": { 37 | "description": "An array of location names that will be appended to the VM and other object names" 38 | } 39 | } 40 | }, 41 | "variables": { 42 | "locationCount": "[length(parameters('locations'))]", 43 | "networkSecurityGroupPrefix": "networkSecurityGroup-", 44 | "addressPrefix": "10.0.0.0/16", 45 | "subnetName": "Subnet", 46 | "subnetPrefix": "10.0.0.0/24", 47 | "vhdStorageType": "Standard_LRS", 48 | "publicIPAddressType": "Dynamic", 49 | "winPublicIPAddressPrefix": "WinPublicIP-", 50 | "vhdStorageContainerName": "vhds", 51 | "virtualNetworkNamePrefix": "VNET-", 52 | "vhdStorageNamePrefix": "[uniqueString(resourceGroup().id)]", 53 | "Win1ImagePublisher": "MicrosoftWindowsServer", 54 | "Win1ImageOffer": "WindowsServer", 55 | "Win1OSDiskName": "Win1OSDisk", 56 | "Win1WindowsOSVersion": "2012-R2-Datacenter", 57 | "Win1VmSize": "Standard_A2", 58 | "Win1StorageAccountContainerName": "vhds", 59 | "NicNamePrefix": "Nic-", 60 | "vmNamePrefix": "WinVM-" 61 | }, 62 | "resources": [ 63 | { 64 | "type": "Microsoft.Storage/storageAccounts", 65 | "name": "[toLower(concat(variables('vhdStorageNamePrefix'),parameters('locationNames')[copyIndex()]))]", 66 | "apiVersion": "2015-06-15", 67 | "location": "[parameters('locations')[copyIndex()]]", 68 | "tags": { 69 | "displayName": "[toLower(concat(variables('vhdStorageNamePrefix'),parameters('dnsNamePrefix'),parameters('locationNames')[copyIndex()]))]" 70 | }, 71 | "properties": { 72 | "accountType": "[variables('vhdStorageType')]" 73 | }, 74 | "copy": { 75 | "name": "storageCopy", 76 | "count": "[variables('locationCount')]" 77 | } 78 | }, 79 | { 80 | "apiVersion": "2015-06-15", 81 | "type": "Microsoft.Network/publicIPAddresses", 82 | "name": "[concat(variables('winPublicIPAddressPrefix'),parameters('locations')[copyIndex()])]", 83 | "location": "[parameters('locations')[copyIndex()]]", 84 | "tags": { 85 | "displayName": "[concat(variables('winPublicIPAddressPrefix'),parameters('locations')[copyIndex()])]" 86 | }, 87 | "properties": { 88 | "publicIPAllocationMethod": "[variables('publicIPAddressType')]", 89 | "dnsSettings": { 90 | "domainNameLabel": "[toLower(concat(parameters('dnsNamePrefix'),parameters('locationNames')[copyIndex()]))]" 91 | } 92 | }, 93 | "copy": { 94 | "name": "publicIPAddressCopy", 95 | "count": "[variables('locationCount')]" 96 | } 97 | }, 98 | { 99 | "type": "Microsoft.Network/networkSecurityGroups", 100 | "name": "[concat(variables('networkSecurityGroupPrefix'),parameters('locations')[copyIndex()])]", 101 | "apiVersion": "2015-06-15", 102 | "location": "[parameters('locations')[copyIndex()]]", 103 | "tags": { 104 | "displayName": "[concat(variables('networkSecurityGroupPrefix'),parameters('locations')[copyIndex()])]" 105 | }, 106 | "properties": { 107 | "securityRules": [ 108 | { 109 | "name": "allow_rdp", 110 | "properties": { 111 | "description": "Allow RDP", 112 | "protocol": "Tcp", 113 | "sourcePortRange": "*", 114 | "destinationPortRange": "3389", 115 | "sourceAddressPrefix": "Internet", 116 | "destinationAddressPrefix": "*", 117 | "access": "Allow", 118 | "priority": 100, 119 | "direction": "Inbound" 120 | } 121 | }, 122 | { 123 | "name": "allow_ssh", 124 | "properties": { 125 | "description": "Allow RDP", 126 | "protocol": "Tcp", 127 | "sourcePortRange": "*", 128 | "destinationPortRange": "22", 129 | "sourceAddressPrefix": "Internet", 130 | "destinationAddressPrefix": "*", 131 | "access": "Allow", 132 | "priority": 101, 133 | "direction": "Inbound" 134 | } 135 | }, 136 | 137 | { 138 | "name": "allow_remote_powershell_http", 139 | "properties": { 140 | "description": "Allow Remote Powershell HTTP", 141 | "protocol": "Tcp", 142 | "sourcePortRange": "*", 143 | "destinationPortRange": "5985", 144 | "sourceAddressPrefix": "Internet", 145 | "destinationAddressPrefix": "*", 146 | "access": "Allow", 147 | "priority": 102, 148 | "direction": "Inbound" 149 | } 150 | }, 151 | { 152 | "name": "allow_remote_powershell_https", 153 | "properties": { 154 | "description": "Allow Remote Powershell HTTPS", 155 | "protocol": "Tcp", 156 | "sourcePortRange": "*", 157 | "destinationPortRange": "5986", 158 | "sourceAddressPrefix": "Internet", 159 | "destinationAddressPrefix": "*", 160 | "access": "Allow", 161 | "priority": 103, 162 | "direction": "Inbound" 163 | } 164 | }, 165 | 166 | 167 | { 168 | "name": "allow_web", 169 | "properties": { 170 | "description": "Allow WEB", 171 | "protocol": "Tcp", 172 | "sourcePortRange": "*", 173 | "destinationPortRange": "80-85", 174 | "sourceAddressPrefix": "Internet", 175 | "destinationAddressPrefix": "*", 176 | "access": "Allow", 177 | "priority": 104, 178 | "direction": "Inbound" 179 | } 180 | }, 181 | { 182 | "name": "allow_web_proxy", 183 | "properties": { 184 | "description": "Allow WEB Proxy", 185 | "protocol": "Tcp", 186 | "sourcePortRange": "*", 187 | "destinationPortRange": "8080", 188 | "sourceAddressPrefix": "Internet", 189 | "destinationAddressPrefix": "*", 190 | "access": "Allow", 191 | "priority": 105, 192 | "direction": "Inbound" 193 | } 194 | }, 195 | { 196 | "name": "allow_kestrel", 197 | "properties": { 198 | "description": "Allow Kestrel", 199 | "protocol": "Tcp", 200 | "sourcePortRange": "*", 201 | "destinationPortRange": "5000-5010", 202 | "sourceAddressPrefix": "Internet", 203 | "destinationAddressPrefix": "*", 204 | "access": "Allow", 205 | "priority": 106, 206 | "direction": "Inbound" 207 | } 208 | } 209 | ] 210 | }, 211 | "copy": { 212 | "name": "networkSecurityGroupCopy", 213 | "count": "[variables('locationCount')]" 214 | } 215 | }, 216 | { 217 | "apiVersion": "2015-06-15", 218 | "type": "Microsoft.Network/virtualNetworks", 219 | "name": "[concat(variables('virtualNetworkNamePrefix'),parameters('locations')[copyIndex()])]", 220 | "location": "[parameters('locations')[copyIndex()]]", 221 | "tags": { 222 | "displayName": "[concat(variables('virtualNetworkNamePrefix'),parameters('locations')[copyIndex()])]" 223 | }, 224 | "dependsOn": [ 225 | "networkSecurityGroupCopy" 226 | ], 227 | "properties": { 228 | "addressSpace": { 229 | "addressPrefixes": [ 230 | "[variables('addressPrefix')]" 231 | ] 232 | }, 233 | "subnets": [ 234 | { 235 | "name": "[variables('subnetName')]", 236 | "properties": { 237 | "addressPrefix": "[variables('subnetPrefix')]", 238 | "networkSecurityGroup": { 239 | "id": "[resourceId('Microsoft.Network/networkSecurityGroups', concat(variables('networkSecurityGroupPrefix'),parameters('locations')[copyIndex()]))]" 240 | } 241 | } 242 | } 243 | ] 244 | }, 245 | "copy": { 246 | "name": "virtualNetworksCopy", 247 | "count": "[variables('locationCount')]" 248 | } 249 | }, 250 | { 251 | "name": "[concat(variables('NicNamePrefix'),parameters('locations')[copyIndex()])]", 252 | "type": "Microsoft.Network/networkInterfaces", 253 | "location": "[parameters('locations')[copyIndex()]]", 254 | "apiVersion": "2015-06-15", 255 | "dependsOn": [ 256 | "publicIPAddressCopy", 257 | "virtualNetworksCopy" 258 | ], 259 | "tags": { 260 | "displayName": "[concat(variables('NicNamePrefix'),parameters('locations')[copyIndex()])]" 261 | }, 262 | "properties": { 263 | "ipConfigurations": [ 264 | { 265 | "name": "ipconfig1", 266 | "properties": { 267 | "privateIPAllocationMethod": "Dynamic", 268 | "publicIPAddress": { 269 | "id": "[resourceId('Microsoft.Network/publicIPAddresses', concat(variables('winPublicIPAddressPrefix'),parameters('locations')[copyIndex()]))]" 270 | }, 271 | "subnet": { 272 | "id": "[concat(resourceId('Microsoft.Network/virtualNetworks', concat(variables('virtualNetworkNamePrefix'),parameters('locations')[copyIndex()])), '/subnets/', variables('subnetName'))]" 273 | } 274 | } 275 | } 276 | ] 277 | }, 278 | "copy": { 279 | "name": "networkInterfacesCopy", 280 | "count": "[variables('locationCount')]" 281 | } 282 | }, 283 | { 284 | "name": "[concat(variables('vmNamePrefix'),parameters('locations')[copyIndex()],'-',toLower(parameters('dnsNamePrefix')),toLower(parameters('locationNames')[copyIndex()]))]", 285 | "type": "Microsoft.Compute/virtualMachines", 286 | "location": "[parameters('locations')[copyIndex()]]", 287 | "apiVersion": "2015-06-15", 288 | "dependsOn": [ 289 | "storageCopy", 290 | "networkInterfacesCopy" 291 | ], 292 | "tags": { 293 | "displayName": "[concat(variables('vmNamePrefix'),parameters('locations')[copyIndex()],'-',parameters('dnsNamePrefix'),parameters('locationNames')[copyIndex()])]" 294 | }, 295 | "properties": { 296 | "hardwareProfile": { 297 | "vmSize": "[variables('Win1VmSize')]" 298 | }, 299 | "osProfile": { 300 | "computerName": "[concat(parameters('dnsNamePrefix'),parameters('locationNames')[copyIndex()])]", 301 | "adminUsername": "[parameters('adminUsername')]", 302 | "adminPassword": "[parameters('adminPassword')]" 303 | }, 304 | "storageProfile": { 305 | "imageReference": { 306 | "publisher": "[variables('Win1ImagePublisher')]", 307 | "offer": "[variables('Win1ImageOffer')]", 308 | "sku": "[variables('Win1WindowsOSVersion')]", 309 | "version": "latest" 310 | }, 311 | "osDisk": { 312 | "name": "Win1OSDisk", 313 | "vhd": { 314 | "uri": "[concat('http://', toLower(concat(variables('vhdStorageNamePrefix'),parameters('locationNames')[copyIndex()])), '.blob.core.windows.net/', variables('Win1StorageAccountContainerName'), '/', variables('Win1OSDiskName'), '.vhd')]" 315 | }, 316 | "caching": "ReadWrite", 317 | "createOption": "FromImage" 318 | } 319 | }, 320 | "networkProfile": { 321 | "networkInterfaces": [ 322 | { 323 | "id": "[resourceId('Microsoft.Network/networkInterfaces', concat(variables('NicNamePrefix'),parameters('locations')[copyIndex()]))]" 324 | } 325 | ] 326 | } 327 | }, 328 | 329 | "resources": [ 330 | { 331 | "apiVersion": "2015-06-15", 332 | "type": "Microsoft.Compute/virtualMachines/extensions", 333 | "name": "[concat(variables('vmNamePrefix'),parameters('locations')[copyIndex()],'-',toLower(parameters('dnsNamePrefix')),toLower(parameters('locationNames')[copyIndex()]),'/WinRMCustomScriptExtension')]", 334 | "location": "[parameters('locations')[copyIndex()]]", 335 | "dependsOn": [ 336 | "[concat('Microsoft.Compute/virtualMachines/', concat(variables('vmNamePrefix'),parameters('locations')[copyIndex()],'-',parameters('dnsNamePrefix'),parameters('locationNames')[copyIndex()]))]" 337 | ], 338 | "properties": { 339 | "publisher": "Microsoft.Compute", 340 | "type": "CustomScriptExtension", 341 | "typeHandlerVersion": "1.4", 342 | "settings": { 343 | "fileUris": [ 344 | "https://raw.githubusercontent.com/Azure/azure-quickstart-templates/master/201-vm-winrm-windows/ConfigureWinRM.ps1", 345 | "https://raw.githubusercontent.com/Azure/azure-quickstart-templates/master/201-vm-winrm-windows/makecert.exe", 346 | "https://raw.githubusercontent.com/Azure/azure-quickstart-templates/master/201-vm-winrm-windows/winrmconf.cmd" 347 | ], 348 | "commandToExecute": "[concat('powershell -ExecutionPolicy Unrestricted -file ConfigureWinRM.ps1 ',concat('*.',parameters('locations')[copyIndex()],'.cloudapp.azure.com'))]" 349 | } 350 | } 351 | } 352 | ], 353 | "copy": { 354 | "name": "virtualMachineCopy", 355 | "count": "[variables('locationCount')]" 356 | } 357 | } 358 | ] 359 | } 360 | -------------------------------------------------------------------------------- /infra/AzureTestCellDeployment/Windows-Linux_CellDeployment.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", 3 | "contentVersion": "1.0.0.0", 4 | "parameters": { 5 | "adminUsername": { 6 | "type": "string", 7 | "minLength": 1, 8 | "metadata": { 9 | "description": "User name for the Virtual Machine." 10 | } 11 | }, 12 | "adminPassword": { 13 | "type": "string", 14 | "metadata": { 15 | "description": "Password for the Virtual Machine." 16 | } 17 | }, 18 | "dnsNamePrefix": { 19 | "type": "string", 20 | "minLength": 1, 21 | "maxLength": 10, 22 | "metadata": { 23 | "description": "Globally unique DNS Name prefix to which the VM names will be appended for to access the Virtual Machine." 24 | } 25 | }, 26 | "ubuntuOSVersion": { 27 | "type": "string", 28 | "defaultValue": "14.04.4-LTS", 29 | "metadata": { 30 | "description": "The Ubuntu version for the VM. This will pick a fully patched image of this given Ubuntu version." 31 | } 32 | } 33 | }, 34 | "variables": { 35 | "globalNetworkSecurityGroupName": "globalNetworkSecurityGroup", 36 | "networkSecurityGroupName": "networkSecurityGroup", 37 | "imagePublisher": "Canonical", 38 | "imageOffer": "UbuntuServer", 39 | "OSDiskName": "osdiskforlinux", 40 | "linuxNicName": "LinuxNic", 41 | "addressPrefix": "10.0.0.0/16", 42 | "subnetName": "Subnet", 43 | "subnetPrefix": "10.0.0.0/24", 44 | "vhdStorageType": "Standard_LRS", 45 | "publicIPAddressType": "Dynamic", 46 | "linuxpublicIPAddressName": "LinuxPublicIP", 47 | "winpublicIPAddressName": "WinPublicIP", 48 | "linuxVmName": "Linux1", 49 | "winVmName": "Win1", 50 | "linuxPublicDnsName": "[toLower(concat(parameters('dnsNamePrefix'),'-',variables('linuxVmName')))]", 51 | "winPublicDnsName": "[toLower(concat(parameters('dnsNamePrefix'),'-',variables('winVmName')))]", 52 | "vhdStorageContainerName": "vhds", 53 | "linuxVmSize": "Standard_A2", 54 | "virtualNetworkName": "VNET", 55 | "vnetId": "[resourceId('Microsoft.Network/virtualNetworks', variables('virtualNetworkName'))]", 56 | "subnetRef": "[concat(variables('vnetId'), '/subnets/', variables('subnetName'))]", 57 | "vhdStorageName": "[concat('vhdstorage', uniqueString(resourceGroup().id))]", 58 | "Win1ImagePublisher": "MicrosoftWindowsServer", 59 | "Win1ImageOffer": "WindowsServer", 60 | "Win1OSDiskName": "Win1OSDisk", 61 | "Win1WindowsOSVersion": "2012-R2-Datacenter", 62 | "Win1VmSize": "Standard_A2", 63 | "Win1SubnetRef": "[concat(variables('vnetId'), '/subnets/', variables('subnetName'))]", 64 | "Win1StorageAccountContainerName": "vhds", 65 | "Win1NicName": "Win1Nic", 66 | "hostDNSNameScriptArgument": "[concat('*.',resourceGroup().location,'.cloudapp.azure.com')]" 67 | }, 68 | "resources": [ 69 | { 70 | "type": "Microsoft.Storage/storageAccounts", 71 | "name": "[variables('vhdStorageName')]", 72 | "apiVersion": "2015-06-15", 73 | "location": "[resourceGroup().location]", 74 | "tags": { 75 | "displayName": "StorageAccount" 76 | }, 77 | "properties": { 78 | "accountType": "[variables('vhdStorageType')]" 79 | } 80 | }, 81 | { 82 | "apiVersion": "2015-06-15", 83 | "type": "Microsoft.Network/publicIPAddresses", 84 | "name": "[variables('linuxpublicIPAddressName')]", 85 | "location": "[resourceGroup().location]", 86 | "tags": { 87 | "displayName": "LinuxPublicIPAddress" 88 | }, 89 | "properties": { 90 | "publicIPAllocationMethod": "[variables('publicIPAddressType')]", 91 | "dnsSettings": { 92 | "domainNameLabel": "[variables('linuxPublicDnsName')]" 93 | } 94 | } 95 | }, 96 | { 97 | "apiVersion": "2015-06-15", 98 | "type": "Microsoft.Network/publicIPAddresses", 99 | "name": "[variables('winpublicIPAddressName')]", 100 | "location": "[resourceGroup().location]", 101 | "tags": { 102 | "displayName": "Win1PublicIPAddress" 103 | }, 104 | "properties": { 105 | "publicIPAllocationMethod": "[variables('publicIPAddressType')]", 106 | "dnsSettings": { 107 | "domainNameLabel": "[variables('winPublicDnsName')]" 108 | } 109 | } 110 | }, 111 | { 112 | "type": "Microsoft.Network/networkSecurityGroups", 113 | "name": "[variables('networkSecurityGroupName')]", 114 | "apiVersion": "2015-06-15", 115 | "location": "[resourceGroup().location]", 116 | "tags": { 117 | "displayName": "NetworkSecurityGroup" 118 | }, 119 | "properties": { 120 | "securityRules": [ 121 | { 122 | "name": "allow_rdp", 123 | "properties": { 124 | "description": "Allow RDP", 125 | "protocol": "Tcp", 126 | "sourcePortRange": "*", 127 | "destinationPortRange": "3389", 128 | "sourceAddressPrefix": "Internet", 129 | "destinationAddressPrefix": "*", 130 | "access": "Allow", 131 | "priority": 100, 132 | "direction": "Inbound" 133 | } 134 | }, 135 | { 136 | "name": "allow_ssh", 137 | "properties": { 138 | "description": "Allow RDP", 139 | "protocol": "Tcp", 140 | "sourcePortRange": "*", 141 | "destinationPortRange": "22", 142 | "sourceAddressPrefix": "Internet", 143 | "destinationAddressPrefix": "*", 144 | "access": "Allow", 145 | "priority": 101, 146 | "direction": "Inbound" 147 | } 148 | }, 149 | 150 | { 151 | "name": "allow_remote_powershell_http", 152 | "properties": { 153 | "description": "Allow Remote Powershell HTTP", 154 | "protocol": "Tcp", 155 | "sourcePortRange": "*", 156 | "destinationPortRange": "5985", 157 | "sourceAddressPrefix": "Internet", 158 | "destinationAddressPrefix": "*", 159 | "access": "Allow", 160 | "priority": 102, 161 | "direction": "Inbound" 162 | } 163 | }, 164 | { 165 | "name": "allow_remote_powershell_https", 166 | "properties": { 167 | "description": "Allow Remote Powershell HTTPS", 168 | "protocol": "Tcp", 169 | "sourcePortRange": "*", 170 | "destinationPortRange": "5986", 171 | "sourceAddressPrefix": "Internet", 172 | "destinationAddressPrefix": "*", 173 | "access": "Allow", 174 | "priority": 103, 175 | "direction": "Inbound" 176 | } 177 | }, 178 | 179 | 180 | { 181 | "name": "allow_web", 182 | "properties": { 183 | "description": "Allow WEB", 184 | "protocol": "Tcp", 185 | "sourcePortRange": "*", 186 | "destinationPortRange": "80-85", 187 | "sourceAddressPrefix": "Internet", 188 | "destinationAddressPrefix": "*", 189 | "access": "Allow", 190 | "priority": 104, 191 | "direction": "Inbound" 192 | } 193 | }, 194 | { 195 | "name": "allow_web_proxy", 196 | "properties": { 197 | "description": "Allow WEB Proxy", 198 | "protocol": "Tcp", 199 | "sourcePortRange": "*", 200 | "destinationPortRange": "8080", 201 | "sourceAddressPrefix": "Internet", 202 | "destinationAddressPrefix": "*", 203 | "access": "Allow", 204 | "priority": 105, 205 | "direction": "Inbound" 206 | } 207 | }, 208 | { 209 | "name": "allow_kestrel", 210 | "properties": { 211 | "description": "Allow Kestrel", 212 | "protocol": "Tcp", 213 | "sourcePortRange": "*", 214 | "destinationPortRange": "5000-5010", 215 | "sourceAddressPrefix": "Internet", 216 | "destinationAddressPrefix": "*", 217 | "access": "Allow", 218 | "priority": 106, 219 | "direction": "Inbound" 220 | } 221 | } 222 | ] 223 | } 224 | }, 225 | { 226 | "apiVersion": "2015-06-15", 227 | "type": "Microsoft.Network/virtualNetworks", 228 | "name": "[variables('virtualNetworkName')]", 229 | "location": "[resourceGroup().location]", 230 | "tags": { 231 | "displayName": "VirtualNetwork" 232 | }, 233 | "dependsOn": [ 234 | "[concat('Microsoft.Network/networkSecurityGroups/', variables('networkSecurityGroupName'))]" 235 | ], 236 | "properties": { 237 | "addressSpace": { 238 | "addressPrefixes": [ 239 | "[variables('addressPrefix')]" 240 | ] 241 | }, 242 | "subnets": [ 243 | { 244 | "name": "[variables('subnetName')]", 245 | "properties": { 246 | "addressPrefix": "[variables('subnetPrefix')]", 247 | "networkSecurityGroup": { 248 | "id": "[resourceId('Microsoft.Network/networkSecurityGroups', variables('networkSecurityGroupName'))]" 249 | } 250 | } 251 | } 252 | ] 253 | } 254 | }, 255 | { 256 | "apiVersion": "2015-06-15", 257 | "type": "Microsoft.Network/networkInterfaces", 258 | "name": "[variables('linuxNicName')]", 259 | "location": "[resourceGroup().location]", 260 | "tags": { 261 | "displayName": "LinuxNic" 262 | }, 263 | "dependsOn": [ 264 | "[concat('Microsoft.Network/publicIPAddresses/', variables('linuxpublicIPAddressName'))]", 265 | "[concat('Microsoft.Network/virtualNetworks/', variables('virtualNetworkName'))]" 266 | ], 267 | "properties": { 268 | "ipConfigurations": [ 269 | { 270 | "name": "ipconfig1", 271 | "properties": { 272 | "privateIPAllocationMethod": "Dynamic", 273 | "publicIPAddress": { 274 | "id": "[resourceId('Microsoft.Network/publicIPAddresses', variables('linuxpublicIPAddressName'))]" 275 | }, 276 | "subnet": { 277 | "id": "[variables('subnetRef')]" 278 | } 279 | } 280 | } 281 | ] 282 | } 283 | }, 284 | { 285 | "name": "[variables('Win1NicName')]", 286 | "type": "Microsoft.Network/networkInterfaces", 287 | "location": "[resourceGroup().location]", 288 | "apiVersion": "2015-06-15", 289 | "dependsOn": [ 290 | "[concat('Microsoft.Network/publicIPAddresses/', variables('winpublicIPAddressName'))]", 291 | "[concat('Microsoft.Network/virtualNetworks/', variables('virtualNetworkName'))]" 292 | ], 293 | "tags": { 294 | "displayName": "Win1Nic" 295 | }, 296 | "properties": { 297 | "ipConfigurations": [ 298 | { 299 | "name": "ipconfig1", 300 | "properties": { 301 | "privateIPAllocationMethod": "Dynamic", 302 | "publicIPAddress": { 303 | "id": "[resourceId('Microsoft.Network/publicIPAddresses', variables('winpublicIPAddressName'))]" 304 | }, 305 | "subnet": { 306 | "id": "[variables('Win1SubnetRef')]" 307 | } 308 | } 309 | } 310 | ] 311 | } 312 | }, 313 | { 314 | "apiVersion": "2015-06-15", 315 | "type": "Microsoft.Compute/virtualMachines", 316 | "name": "[variables('linuxVmName')]", 317 | "location": "[resourceGroup().location]", 318 | "tags": { 319 | "displayName": "LinuxVM" 320 | }, 321 | "dependsOn": [ 322 | "[concat('Microsoft.Storage/storageAccounts/', variables('vhdStorageName'))]", 323 | "[concat('Microsoft.Network/networkInterfaces/', variables('linuxNicName'))]" 324 | ], 325 | "properties": { 326 | "hardwareProfile": { 327 | "vmSize": "[variables('linuxVmSize')]" 328 | }, 329 | "osProfile": { 330 | "computerName": "[variables('linuxPublicDnsName')]", 331 | "adminUsername": "[parameters('adminUsername')]", 332 | "adminPassword": "[parameters('adminPassword')]" 333 | }, 334 | "storageProfile": { 335 | "imageReference": { 336 | "publisher": "[variables('imagePublisher')]", 337 | "offer": "[variables('imageOffer')]", 338 | "sku": "[parameters('ubuntuOSVersion')]", 339 | "version": "latest" 340 | }, 341 | "osDisk": { 342 | "name": "LinuxOsdisk", 343 | "vhd": { 344 | "uri": "[concat('http://', variables('vhdStorageName'), '.blob.core.windows.net/', variables('vhdStorageContainerName'), '/', variables('OSDiskName'), '.vhd')]" 345 | }, 346 | "caching": "ReadWrite", 347 | "createOption": "FromImage" 348 | } 349 | }, 350 | "networkProfile": { 351 | "networkInterfaces": [ 352 | { 353 | "id": "[resourceId('Microsoft.Network/networkInterfaces', variables('linuxNicName'))]" 354 | } 355 | ] 356 | } 357 | } 358 | }, 359 | { 360 | "name": "[variables('winVmName')]", 361 | "type": "Microsoft.Compute/virtualMachines", 362 | "location": "[resourceGroup().location]", 363 | "apiVersion": "2015-06-15", 364 | "dependsOn": [ 365 | "[concat('Microsoft.Storage/storageAccounts/', variables('vhdStorageName'))]", 366 | "[concat('Microsoft.Network/networkInterfaces/', variables('Win1NicName'))]" 367 | ], 368 | "tags": { 369 | "displayName": "WinVM" 370 | }, 371 | "properties": { 372 | "hardwareProfile": { 373 | "vmSize": "[variables('Win1VmSize')]" 374 | }, 375 | "osProfile": { 376 | "computerName": "[variables('winPublicDnsName')]", 377 | "adminUsername": "[parameters('adminUsername')]", 378 | "adminPassword": "[parameters('adminPassword')]" 379 | }, 380 | "storageProfile": { 381 | "imageReference": { 382 | "publisher": "[variables('Win1ImagePublisher')]", 383 | "offer": "[variables('Win1ImageOffer')]", 384 | "sku": "[variables('Win1WindowsOSVersion')]", 385 | "version": "latest" 386 | }, 387 | "osDisk": { 388 | "name": "Win1OSDisk", 389 | "vhd": { 390 | "uri": "[concat('http://', variables('vhdStorageName'), '.blob.core.windows.net/', variables('Win1StorageAccountContainerName'), '/', variables('Win1OSDiskName'), '.vhd')]" 391 | }, 392 | "caching": "ReadWrite", 393 | "createOption": "FromImage" 394 | } 395 | }, 396 | "networkProfile": { 397 | "networkInterfaces": [ 398 | { 399 | "id": "[resourceId('Microsoft.Network/networkInterfaces', variables('Win1NicName'))]" 400 | } 401 | ] 402 | } 403 | }, 404 | 405 | "resources": [ 406 | { 407 | "apiVersion": "2015-06-15", 408 | "type": "Microsoft.Compute/virtualMachines/extensions", 409 | "name": "[concat(variables('winVmName'),'/WinRMCustomScriptExtension')]", 410 | "location": "[resourceGroup().location]", 411 | "dependsOn": [ 412 | "[concat('Microsoft.Compute/virtualMachines/', variables('winVmName'))]" 413 | ], 414 | "properties": { 415 | "publisher": "Microsoft.Compute", 416 | "type": "CustomScriptExtension", 417 | "typeHandlerVersion": "1.4", 418 | "settings": { 419 | "fileUris": [ 420 | "https://raw.githubusercontent.com/Azure/azure-quickstart-templates/master/201-vm-winrm-windows/ConfigureWinRM.ps1", 421 | "https://raw.githubusercontent.com/Azure/azure-quickstart-templates/master/201-vm-winrm-windows/makecert.exe", 422 | "https://raw.githubusercontent.com/Azure/azure-quickstart-templates/master/201-vm-winrm-windows/winrmconf.cmd" 423 | ], 424 | "commandToExecute": "[concat('powershell -ExecutionPolicy Unrestricted -file ConfigureWinRM.ps1 ',variables('hostDNSNameScriptArgument'))]" 425 | } 426 | } 427 | } 428 | ] 429 | 430 | 431 | } 432 | ] 433 | } 434 | -------------------------------------------------------------------------------- /infra/AzureTestCellDeployment/WindowsVmSetup-Core.ps1: -------------------------------------------------------------------------------- 1 | 2 | Param( 3 | [string] [Parameter(Mandatory=$true)] $AdminUser, 4 | [string] [Parameter(Mandatory=$true)] $AdminPassword, 5 | [string] [Parameter(Mandatory=$true)] $MqttBroker, 6 | [string] [Parameter(Mandatory=$true)] $MqttUser, 7 | [string] [Parameter(Mandatory=$true)] $MqttPassword 8 | ) 9 | 10 | # Turn off firewall. Azure security group settings control external access. Intra Test Cell connections should be available 11 | & netsh advfirewall set allprofiles state off 12 | 13 | mkdir c:\WinConfig 14 | 15 | #install Git 16 | Invoke-WebRequest -Uri "https://github.com/git-for-windows/git/releases/download/v2.8.1.windows.1/Git-2.8.1-64-bit.exe" -OutFile "c:\WinConfig\Git-2.8.1-64-bit.exe" -TimeoutSec 180 -ErrorAction:Stop -Verbose 17 | & c:\WinConfig\Git-2.8.1-64-bit.exe /Silent | Out-Null -Verbose 18 | $env:Path += ";C:\Program Files\Git" 19 | 20 | #install nodejs 21 | Invoke-WebRequest -Uri "https://nodejs.org/dist/v4.4.3/node-v4.4.3-x64.msi" -OutFile "c:\WinConfig\node-v4.4.3-x64.msi" -TimeoutSec 180 -ErrorAction:Stop -Verbose 22 | & msiexec /i c:\WinConfig\node-v4.4.3-x64.msi /quiet | Out-Null -Verbose 23 | $env:Path += ";C:\Program Files\nodejs;c:\Users\" + $AdminUser + "\AppData\Roaming\npm" 24 | 25 | #configure autologon 26 | ..\scripts\autoLogon.ps1 -AdminUser $AdminUser -AdminPassword $AdminPassword 27 | 28 | mkdir c:\StartupConfig -Force 29 | 30 | #configure logon startup script 31 | $startUpBatchFileContent = @" 32 | powershell.exe -command "& {[System.IO.Directory]::Delete('c:\Wave',1) }" 2>&1 > C:\StartupConfig\WaveDelete.log 33 | powershell.exe -NoProfile -ExecutionPolicy unrestricted -Command "&{`$target='c:\Wave\';`$broker='$MqttBroker';`$username='$MqttUser';`$password='$MqttPassword';iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/aspnet/Wave/master/scripts/Install.ps1'))}" 2>&1 > c:\StartupConfig\WaveInstall.log 34 | "@ 35 | 36 | $startUpBatchFileContent | Set-Content c:\StartupConfig\Startup.bat 37 | 38 | $startupRegContent = @" 39 | Windows Registry Editor Version 5.00 40 | 41 | [HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Run] 42 | "startup"="C:\\StartupConfig\\Startup.bat" 43 | "@ 44 | $startupRegContent | Set-Content c:\StartupConfig\EnableStartup.reg 45 | $result = cmd /c regedit.exe /S c:\StartupConfig\EnableStartup.reg 2`>`&1 46 | $result = "Startup config complete " + $result 47 | Write-Output $result 48 | 49 | -------------------------------------------------------------------------------- /infra/AzureTestCellManagement/ShutDownVMsInRescourceGroup.ps1: -------------------------------------------------------------------------------- 1 | Param( 2 | [string] [Parameter(Mandatory=$true)] $SubscriptionId, 3 | [string] [Parameter(Mandatory=$true)] $ResourceGroupName 4 | ) 5 | 6 | $currentSubscription = (Get-AzureRmContext).Subscription 7 | if ($currentSubscription.SubscriptionId -ne $SubscriptionId) 8 | { 9 | Write-Host "Setting current subscription" 10 | Select-AzureRmSubscription -SubscriptionID $SubscriptionId 11 | } 12 | 13 | 14 | $vmList = Get-AzureRmVM -ResourceGroupName $ResourceGroupName 15 | if ( -not $vmList -or $vmList.Count -le 0) 16 | { 17 | Write-Host "Error: No VMs were found for the Resource Group: $ResourceGroupName" 18 | return 19 | } 20 | 21 | Write-Host "" 22 | Write-Host "Discovered the following VMs in ResourceGroup $ResourceGroupName" 23 | foreach ($vm in $vmList) 24 | { 25 | Write-Host $vm.Name 26 | } 27 | 28 | Write-Host "" 29 | foreach ($vm in $vmList) 30 | { 31 | Write-Host Stopping $vm.Name 32 | Stop-AzureRmVM -ResourceGroupName $ResourceGroupName -Name $vm.Name -Force 33 | } 34 | 35 | 36 | -------------------------------------------------------------------------------- /infra/README.md: -------------------------------------------------------------------------------- 1 | # You've added your first Readme file! 2 | A README.md file is intended to quickly orient readers to what your project can do. New to Markdown? [Learn more](http://go.microsoft.com/fwlink/p/?LinkId=524306&clcid=0x409) 3 | 4 | ## Edit this README and commit your change to a topic branch 5 | In Git, branches are cheap. You should use them whenever you're making changes to your repository. Edit this file by clicking on the edit icon. 6 | 7 | Then make some changes to this README file. 8 | 9 | > Make some **edits** to _this_ blockquote 10 | 11 | When you are done, click the dropdown arrow next to the save button - that will allow you to commit your changes to a new branch. 12 | 13 | ## Create a pull request to contribute your changes back into master 14 | Pull requests are the way to move changes from a topic branch back into the master branch. 15 | 16 | Click on the **Pull Requests** page in the **CODE** hub, then click "New Pull Request" to create a new pull request from your topic branch to the master branch. 17 | 18 | When you are done adding details, click "Create Pull request". Once a pull request is sent, reviewers can see your changes, recommend modifications, or even push follow-up commits. 19 | 20 | First time creating a pull request? [Learn more](http://go.microsoft.com/fwlink/?LinkId=533211&clcid=0x409) 21 | 22 | ### Congratulations! You've completed the grand tour of the CODE hub! 23 | 24 | # Next steps 25 | 26 | If you haven't done so yet: 27 | * [Install Visual Studio](http://go.microsoft.com/fwlink/?LinkId=309297&clcid=0x409&slcid=0x409) 28 | * [Install Git](http://git-scm.com/downloads) 29 | 30 | Then clone this repo to your local machine to get started with your own project. 31 | 32 | Happy coding! -------------------------------------------------------------------------------- /jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | // See http://go.microsoft.com/fwlink/?LinkId=759670 3 | // for the documentation about the jsconfig.json format 4 | "compilerOptions": { 5 | "target": "es6" 6 | }, 7 | "exclude": [ 8 | "node_modules", 9 | "bower_components", 10 | "jspm_packages", 11 | "tmp", 12 | "temp" 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /libs/credUtil.js: -------------------------------------------------------------------------------- 1 | module.exports.clearEnv = function(){ 2 | delete process.env["BROKER"]; 3 | delete process.env["USERNAME"]; 4 | delete process.env["PASSWORD"]; 5 | } -------------------------------------------------------------------------------- /libs/env.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | 4 | function udpateConfig(key, value) { 5 | var config = getConfig() || {}; 6 | 7 | // If the valid defined value has been passed then 8 | // update the value in the config object. 9 | if (typeof (value) !== 'undefined') { 10 | config[key] = value; 11 | } 12 | 13 | // Clear the keys only if the value is null 14 | if (value == "") { 15 | delete config[key]; 16 | } 17 | 18 | _configValues = config; 19 | } 20 | 21 | function getConfig() { 22 | if (!_configValues) { 23 | try { 24 | var fstats = fs.statSync(_configFilename); 25 | if (fstats && fstats.isFile()) { 26 | var obj = JSON.parse(fs.readFileSync(_configFilename, 'utf8')); 27 | _configValues = obj; 28 | } 29 | } catch (e) { } 30 | } 31 | 32 | return _configValues; 33 | } 34 | 35 | function get(commandEnv) { 36 | var env = getEnv(commandEnv); 37 | if (env) { 38 | var envPath = getPath(env); 39 | if (envPath) { 40 | if ((process.platform === 'win32')) { 41 | delete env.Path; 42 | env.PATH = envPath; // NODEJS spawn path is case sensitive and is `PATH`. 43 | } else { 44 | env.PATH = envPath; 45 | } 46 | } 47 | 48 | //TODO : Fix the same for non-windows platforms. 49 | resolveDependentVars(env); 50 | } 51 | 52 | return env || process.env; 53 | } 54 | 55 | function resolveDependentVars(env) { 56 | for (var k in env) { 57 | env[k] = resolveVariabale(env[k], env) 58 | } 59 | 60 | return env; 61 | } 62 | 63 | function resolveVariabale(input, env) { 64 | if (!input) 65 | return; 66 | 67 | if (process.platform === 'win32') { 68 | return input.replace(/%([^%]+)%/g, function (_, n) { 69 | return env[n] || ("%" + n + "%"); 70 | }); 71 | } 72 | return input; 73 | } 74 | 75 | function set(env, path, cwd) { 76 | try { 77 | var cwd = getCwd(cwd); 78 | 79 | udpateConfig("env", env); 80 | udpateConfig('extraPaths', path); 81 | udpateConfig('cwd', cwd); 82 | 83 | var configStr = JSON.stringify(_configValues, null, '\t'); 84 | fs.writeFileSync(_configFilename, configStr); 85 | console.log("Environment Configuration written to " + _configFilename); 86 | return true; 87 | 88 | } catch (e) { 89 | console.log(e); 90 | } 91 | return false; 92 | } 93 | 94 | function getCwd(directory) { 95 | //Check if we are trying to unset the directory. 96 | if (directory == "") { 97 | return directory; 98 | } 99 | 100 | var configCwd = (getConfig() || {})["cwd"]; 101 | var cwd = directory; 102 | if (cwd) { 103 | if ((path.isAbsolute(cwd) == false) && (configCwd)) { 104 | cwd = path.join(configCwd, cwd); 105 | } 106 | } else { 107 | // No overriding path and hence use the configured CWD. 108 | cwd = configCwd; 109 | } 110 | 111 | if (cwd) { 112 | // throw because you can't set a cwd that would 113 | // mess up the agent. 114 | var fstats = fs.statSync(cwd); 115 | if (!fstats || !fstats.isDirectory()) { 116 | throw Error("Not a valid directory") 117 | } 118 | 119 | cwd = path.resolve(cwd); 120 | } 121 | 122 | return cwd; 123 | } 124 | 125 | function getEnv(commandEnv) { 126 | var config = getConfig(); 127 | // Merge if we need to modify the process environment variables. 128 | var shouldClone = (config && config['env']) || (config && config['extraPaths']) || commandEnv; 129 | if (shouldClone) { 130 | var all = {}; 131 | merge(all, process.env); 132 | merge(all, config ? config.env : null); 133 | merge(all, commandEnv); 134 | return all; 135 | } 136 | 137 | return null; 138 | } 139 | 140 | function merge(all, obj) { 141 | if (obj) { 142 | for (var k in obj) { 143 | all[k] = obj[k]; 144 | } 145 | } 146 | } 147 | 148 | function getPath(env) { 149 | var config = getConfig(); 150 | var extraPaths = config ? config['extraPaths'] : null; 151 | if (extraPaths) { 152 | return extraPaths + path.delimiter + (env.PATH || env.Path); 153 | } 154 | 155 | return null; 156 | } 157 | 158 | 159 | var _configFilename = "config-env.json"; 160 | var _configValues = null; 161 | 162 | function setFilename(filename) { 163 | _configFilename = filename; 164 | } 165 | 166 | module.exports.set = set; 167 | module.exports.get = get; 168 | module.exports.getCwd = getCwd; 169 | module.exports.resolve = resolveVariabale 170 | module.exports.setFilename = setFilename 171 | 172 | -------------------------------------------------------------------------------- /libs/ipUtil.js: -------------------------------------------------------------------------------- 1 | var os = require('os'); 2 | 3 | module.exports.getIPs = function () { 4 | var ifaces = os.networkInterfaces(); 5 | var ips = []; 6 | Object.keys(ifaces).forEach(function (ifname) { 7 | var alias = 0; 8 | ifaces[ifname].forEach(function (iface) { 9 | if ('IPv4' !== iface.family || iface.internal !== false) { 10 | // skip over internal (i.e. 127.0.0.1) and non-ipv4 addresses 11 | return; 12 | } 13 | ips.push(iface.address); 14 | if (alias >= 1) { 15 | // this single interface has multiple ipv4 addresses 16 | console.log(ifname + ':' + alias, iface.address); 17 | } else { 18 | // this interface has only one ipv4 adress 19 | console.log(ifname, iface.address); 20 | } 21 | ++alias; 22 | }); 23 | }); 24 | return ips; 25 | }; -------------------------------------------------------------------------------- /libs/log.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs-extra'); 2 | const util = require('util'); 3 | const path = require('path'); 4 | const os = require('os'); 5 | 6 | function set(logpath) { 7 | var fullpath = path.resolve(logpath); 8 | var exists = dirExists(fullpath); 9 | if (exists) { 10 | var configStr = JSON.stringify(fullpath, null, '\t'); 11 | fs.writeFile(_configFilename, configStr, function (err) { 12 | if (err) { 13 | return console.log(err); 14 | } 15 | 16 | console.log("Logpath written to " + _configFilename); 17 | }); 18 | 19 | _logpath = fullpath; 20 | } 21 | 22 | return exists; 23 | } 24 | 25 | function dirExists(fullpath) { 26 | var isDir = false; 27 | try { 28 | return fs.ensureDirSync(fullpath); 29 | } catch (e) { } 30 | 31 | return false; 32 | } 33 | 34 | function get() { 35 | if (!_logpath) { 36 | try { 37 | var fstats = fs.statSync(_configFilename); 38 | if (fstats && fstats.isFile()) { 39 | var fullpath = JSON.parse(fs.readFileSync(_configFilename, 'utf8')); 40 | if (dirExists(fullpath)) { 41 | _logpath = fullpath; 42 | } 43 | } 44 | } catch (e) { 45 | } 46 | } 47 | return _logpath; 48 | } 49 | 50 | var _configFilename = path.resolve("config-log.json"); 51 | var _logpath = null; 52 | 53 | function init(localdir) { 54 | var current = get(); 55 | if (!current) { 56 | var initdir = path.resolve(localdir); 57 | if (!fs.existsSync(initdir)) { 58 | fs.mkdirSync(initdir); 59 | } 60 | 61 | _logpath = initdir 62 | } 63 | } 64 | 65 | function resolveFilename(pid, logfile) { 66 | var filename = null; 67 | 68 | if (logfile) { 69 | var extension = path.extname(logfile); 70 | var name = extension ? path.basename(logfile, extension) : logfile; 71 | filename = util.format("%s_%s%s", name, pid, extension|| ".txt"); 72 | } 73 | else { 74 | filename = util.format("agent_%s_log.txt", pid); 75 | } 76 | 77 | var fullpath = path.resolve(_logpath, filename); 78 | return fullpath; 79 | } 80 | 81 | function setConfigFilePath(filename) { 82 | _configFilename = filename; 83 | } 84 | 85 | module.exports.setFilename = setConfigFilePath; 86 | module.exports.init = init 87 | module.exports.setlogdir = set; 88 | module.exports.resolveFilename = resolveFilename; 89 | 90 | -------------------------------------------------------------------------------- /libs/objUtil.js: -------------------------------------------------------------------------------- 1 | module.exports.extend = function (obj, extra) { 2 | var clone = JSON.parse(JSON.stringify(obj)); 3 | for (var i in extra) { 4 | clone[i] = extra[i]; 5 | } 6 | return clone; 7 | }; -------------------------------------------------------------------------------- /libs/recover.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | 3 | 4 | const isInterruptRegex = ( /^win/.test(process.platform) ? /^[\s]*shutdown\s+/ : /^[\s]*sudo[\s]+shutdown\s+/ ); 5 | const backupFileLocation = '.recovery' 6 | 7 | function isInterrupt(cmd) { 8 | return isInterruptRegex.test(cmd.toLowerCase()) 9 | } 10 | 11 | function backup(msg) { 12 | var cmd = msg.payload; 13 | if (isInterrupt(cmd)) { 14 | console.log("Shutdown received.") 15 | var msgStr = JSON.stringify(msg); 16 | fs.writeFileSync(backupFileLocation, msgStr); 17 | } 18 | return msg; 19 | } 20 | 21 | function interrupt(msg) { 22 | var cmd = msg.requestpayload.command; 23 | if (isInterrupt(cmd) && msg.payload.exitcode == 0) { 24 | return null; 25 | } 26 | else { 27 | return msg; 28 | } 29 | } 30 | 31 | function cleanup(msg) { 32 | if (fs.existsSync(backupFileLocation)) { 33 | fs.unlinkSync(backupFileLocation); 34 | } 35 | return msg; 36 | } 37 | 38 | function recover(msg) { 39 | if (fs.existsSync(backupFileLocation)) { 40 | var msgStr = fs.readFileSync(backupFileLocation); 41 | var msg = JSON.parse(msgStr); 42 | // since we checked for exitcode == 0 before we interrupt 43 | // We can claim that the exit code was 0 and send it to the topic 44 | msg.payload = { exitcode : 0 } 45 | return msg; 46 | } 47 | else { 48 | return null; 49 | } 50 | } 51 | 52 | module.exports.backup = backup; 53 | module.exports.interrupt = interrupt; 54 | module.exports.cleanup = cleanup; 55 | module.exports.recover = recover; 56 | -------------------------------------------------------------------------------- /libs/setenvNode.js: -------------------------------------------------------------------------------- 1 | const env = require('./env'); 2 | const logutil = require('./log'); 3 | 4 | module.exports.set = function (msg) { 5 | msg.payload = 'hostname'; 6 | 7 | var req = msg.requestpayload; 8 | var options = req ? req.options : null; 9 | 10 | if (options) { 11 | if (options.logdir) { 12 | var created = logutil.setlogdir(options.logdir) 13 | if (created === false) { 14 | msg.payload = "ERR_NOLOGDIR"; 15 | } 16 | } 17 | 18 | var success = env.set(options.env, options.path, options.cwd); 19 | if (success === false) { 20 | msg.payload = "ERR_INCORRECT_ENV" 21 | return msg; 22 | } 23 | } 24 | 25 | return msg; 26 | } -------------------------------------------------------------------------------- /libs/setupNode.js: -------------------------------------------------------------------------------- 1 | const envUtil = require('./env') 2 | 3 | module.exports.setup = function (msg) { 4 | var payload = msg.payload; 5 | 6 | // Parse JSON and fetch out the cmd and 7 | // callback topic. 8 | try { 9 | console.log(msg.payload); 10 | payload = JSON.parse(msg.payload); 11 | //Successfully parsed JSON and hence is a complex command; 12 | msg.callbacktopic = payload.callbacktopic; 13 | } catch (e) { 14 | console.log("Simple command Received"); 15 | payload = { command: payload }; 16 | } 17 | 18 | //Setup environment variables and cwd; 19 | // For simple commands .env .cwd will be undefined. 20 | try { 21 | 22 | //Set the requestpayload 23 | msg.requestpayload = payload; 24 | 25 | //Fix environment variables. 26 | var envVars = envUtil.get(payload.env); 27 | msg.env = envVars; 28 | 29 | // Set the current working directory. 30 | // Configuration commands use the cwd to store the value 31 | var directory = envUtil.getCwd(payload.cwd); 32 | msg.cwd = envUtil.resolve(directory, envVars); 33 | 34 | //Simple commands have the command in the payload. 35 | msg.payload = envUtil.resolve(payload.command, envVars); 36 | 37 | } catch (e) { 38 | console.log("Exception during environment setup " + e) 39 | } 40 | 41 | return msg; 42 | }; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cmdport", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "app.js", 6 | "dependencies": { 7 | "express": "^4.13.4", 8 | "fs-extra": "^0.30.0", 9 | "minimist": "^1.2.0", 10 | "node-red": "^0.13.4", 11 | "node-red-contrib-execute": "^1.0.8", 12 | "node-red-contrib-mqtt-dynamic": "^1.0.9", 13 | "process": "^0.11.2" 14 | }, 15 | "devDependencies": {}, 16 | "scripts": { 17 | "test": "echo \"Error: no test specified\" && exit 1" 18 | }, 19 | "author": "Sajay Antony", 20 | "license": "Apache-2.0" 21 | } 22 | -------------------------------------------------------------------------------- /scripts/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:argon 2 | RUN apt-get update && apt-get install git 3 | RUN git clone http://github.com/aspnet/wave 4 | WORKDIR /wave 5 | RUN npm install 6 | RUN ./scripts/write_version.sh 7 | EXPOSE 8000 8 | CMD node setup.js -h ${BROKER} -p ${PORT} -u ${USERNAME} -P ${PASSWORD} --id ${CLIENTID} && node app.js 9 | -------------------------------------------------------------------------------- /scripts/Dockerfile.debug: -------------------------------------------------------------------------------- 1 | FROM node:argon 2 | WORKDIR /wave 3 | EXPOSE 8000 4 | CMD node --debug app.js 5 | -------------------------------------------------------------------------------- /scripts/Install.ps1: -------------------------------------------------------------------------------- 1 | param( 2 | [string]$url = "https://coreperf.blob.core.windows.net/cmdport/win/cmdport.zip", 3 | [ValidateScript({[System.IO.Path]::IsPathRooted($_)})] 4 | [string]$target_dir = $target, 5 | [string]$broker_addr = $broker, 6 | [string]$broker_port = $port, 7 | [string]$broker_username = $username, 8 | [string]$broker_password = $password 9 | ) 10 | 11 | if(test-path $target_dir) 12 | { 13 | Write-Error "$target_dir already exists" 14 | exit 1; 15 | } 16 | 17 | mkdir -force $target_dir 18 | $zipfilePath = Join-Path $target_dir "_temp_download.zip" 19 | 20 | "Downloading [$url]`n" 21 | $client = new-object System.Net.WebClient 22 | $client.DownloadFile( $url, $zipfilePath ) 23 | 24 | "Unzipping to [$target_dir]`n" 25 | #Load the assembly 26 | [System.Reflection.Assembly]::LoadWithPartialName("System.IO.Compression.FileSystem") | Out-Null 27 | #Unzip the file 28 | [System.IO.Compression.ZipFile]::ExtractToDirectory($zipfilePath, $target_dir); 29 | 30 | pushd $target_dir 31 | node setup.js -h $broker_addr -p $broker_port $ -u $broker_username -P $broker_password 32 | 33 | npm install -g forever 34 | forever start app.js 35 | -------------------------------------------------------------------------------- /scripts/Pack.ps1: -------------------------------------------------------------------------------- 1 | #Check 7Zip before running anything. 2 | if (-not (test-path "$env:ProgramFiles\7-Zip\7z.exe")) {throw "$env:ProgramFiles\7-Zip\7z.exe needed"} 3 | 4 | function npmdedupe($dir){ 5 | pushd $dir 6 | npm install 7 | Write-Host NPM dedupe $PWD 8 | npm dedupe 9 | popd 10 | } 11 | 12 | $sourceDir = Resolve-Path ( Join-Path $PSScriptRoot ".." ) 13 | $artifactsDir = Join-Path $sourceDir "artifacts\" 14 | $zipfile = Join-Path $sourceDir "artifacts\win\cmdport.zip" 15 | 16 | #flatten the packages. 17 | npmdedupe($sourceDir) 18 | npmdedupe(Join-Path $sourceDir "client") 19 | 20 | If(Test-path $zipfile) {Remove-item $zipfile} 21 | 22 | $versionFile = Join-Path $artifactsDir "version.json" 23 | new-item -force -path $versionFile -value "" -type file 24 | git log -n1 --format='{"sha" : "%h"}' | Out-File -FilePath $versionFile -Encoding ascii 25 | 26 | #Zip files. 27 | Write-Host Packing [$sourceDir] into [$zipfile] 28 | 29 | #remove root dir for 7zip. 30 | $sourceDir7zip = Join-Path $sourceDir "*"; 31 | if (-not (test-path "$env:ProgramFiles\7-Zip\7z.exe")) {throw "$env:ProgramFiles\7-Zip\7z.exe needed"} 32 | set-alias sz "$env:ProgramFiles\7-Zip\7z.exe" 33 | sz a -mx=9 "-x!_creds.js" "-x!_envVars.json" "-x!_logdir.json.js" $zipfile $sourceDir7zip -------------------------------------------------------------------------------- /scripts/autoLogon.ps1: -------------------------------------------------------------------------------- 1 | 2 | Param( 3 | [string] $AdminDomain, 4 | [string] [Parameter(Mandatory=$true)] $AdminUser, 5 | [string] [Parameter(Mandatory=$true)] $AdminPassword 6 | ) 7 | 8 | mkdir c:\StartupConfig -Force 9 | 10 | $autoLogonRegContent = @" 11 | Windows Registry Editor Version 5.00 12 | 13 | [HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon] 14 | "DefaultUserName"="" 15 | "DefaultPassword"="" 16 | "AutoAdminLogon"="1" 17 | "@ 18 | 19 | if ($AdminDomain) { 20 | $autoLogonRegContent += @" 21 | 22 | "DefaultDomainName"="" 23 | "@ 24 | } 25 | 26 | $autoLogonRegContent = $autoLogonRegContent -replace "", $AdminUser 27 | $autoLogonRegContent = $autoLogonRegContent -replace "", $AdminPassword 28 | $autoLogonRegContent = $autoLogonRegContent -replace "", $AdminDomain 29 | 30 | 31 | $autoLogonRegContent | Set-Content c:\StartupConfig\EnableAutoLogon.reg 32 | $result = cmd /c regedit.exe /S c:\StartupConfig\EnableAutoLogon.reg 2`>`&1 33 | -------------------------------------------------------------------------------- /scripts/cmdport: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | #/etc/init.d/cmdport 3 | 4 | ### BEGIN INIT INFO 5 | # Provides: scriptname 6 | # Required-Start: $remote_fs $syslog 7 | # Required-Stop: $remote_fs $syslog 8 | # Default-Start: 2 3 4 5 9 | # Default-Stop: 0 1 6 10 | # Short-Description: Start daemon at boot time 11 | # Description: Enable service provided by daemon. 12 | ### END INIT INFO 13 | 14 | export PATH=$PATH:/usr/local/bin 15 | export NODE_PATH=$NODE_PATH:/usr/local/lib/node_modules 16 | export HOME={home} 17 | 18 | case "$1" in 19 | start) 20 | exec forever --sourceDir={installpath} -p {home}/.forever app.js 21 | ;; 22 | stop) 23 | exec forever stop --sourceDir={installpath} app.js 24 | ;; 25 | *) 26 | echo "Usage: /etc/init.d/cmdport {start|stop}" 27 | exit 1 28 | ;; 29 | esac 30 | 31 | exit 0 32 | -------------------------------------------------------------------------------- /scripts/docker-debug.sh: -------------------------------------------------------------------------------- 1 | pushd `dirname $0` > /dev/null 2 | docker build -t wave:debug --file Dockerfile.debug . 3 | docker rm -f wave 4 | docker run -it --net="host" -p 8000:8000/tcp -v `readlink -f ..`:/wave --name wave "$@" wave:debug -------------------------------------------------------------------------------- /scripts/docker-run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ "$#" -ne 5 ]; then 4 | echo "Usage: ./docker-run.sh {broker} {port} {username} {password} {hostname}" 5 | exit 1 6 | fi 7 | 8 | _BROKER=$1 9 | _PORT=$2 10 | _USERNAME=$3 11 | _PASSWORD=$4 12 | _HOSTNAME=$5 13 | _CONTAINERID=$(docker run -e BROKER=$_BROKER -e PORT=$_PORT -e USERNAME=$_USERNAME -e PASSWORD=$_PASSWORD -h $_HOSTNAME -d dotnetperf/wave) 14 | sleep 2 15 | docker logs $_CONTAINERID -------------------------------------------------------------------------------- /scripts/install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ "$#" -ne 4 ]; then 4 | echo "Usage: ./install.sh {broker} {port} {username} {password}" 5 | exit 1 6 | fi 7 | 8 | pushd `dirname $0` > /dev/null 9 | 10 | #Install dependencies. 11 | sudo apt-get -y install mosquitto-clients 12 | sudo apt-get -y install git 13 | sudo apt-get -y install nodejs 14 | sudo apt-get -y install npm 15 | sudo ln -s /usr/bin/nodejs /usr/sbin/node 16 | 17 | #install forever. 18 | sudo npm install -g forever 19 | 20 | #Ensure removing the cmdport that is existing if any at all. 21 | sudo update-rc.d -f cmdport remove 22 | 23 | #Move upto the root to install packages for cmdport 24 | pushd ../ 25 | sudo npm install 26 | 27 | INSTALL_PATH=`pwd` 28 | echo ========================================== 29 | echo "INSTALL PATH = $INSTALL_PATH" 30 | echo "USER HOME = $HOME" 31 | echo ========================================== 32 | 33 | #Setup the credentials 34 | ./setup.js -h $1 -p $2 -u $3 -P $4 35 | 36 | #SETUP home and install path variables in the init.d script 37 | sed -e "s#{installpath}#$INSTALL_PATH#g" -e "s#{home}#$HOME#g" ./scripts/cmdport > _cmdport 38 | sudo mv _cmdport /etc/init.d/cmdport 39 | cat /etc/init.d/cmdport 40 | sudo chmod 755 /etc/init.d/cmdport 41 | sudo update-rc.d cmdport defaults 42 | sudo forever list 43 | sudo /etc/init.d/cmdport start /dev/null & 44 | -------------------------------------------------------------------------------- /scripts/install_autoStart.ps1: -------------------------------------------------------------------------------- 1 | param( 2 | [string] $AdminDomain, 3 | [string] [Parameter(Mandatory=$true)] $AdminUser, 4 | [string] [Parameter(Mandatory=$true)] $AdminPassword 5 | ) 6 | 7 | npm install -g forever 8 | 9 | #configure autologon 10 | ..\scripts\autoLogon.ps1 -AdminDomain $AdminDomain -AdminUser $AdminUser -AdminPassword $AdminPassword 11 | 12 | mkdir c:\StartupConfig -Force 13 | 14 | $waveRoot = Resolve-Path (Join-Path $PSScriptRoot "..") 15 | $startUpBatchFileContent = @" 16 | pushd $waveRoot 17 | forever start app.js 18 | "@ 19 | 20 | $startUpBatchFileContent | Set-Content c:\StartupConfig\Startup.bat 21 | 22 | $startupRegContent = @" 23 | Windows Registry Editor Version 5.00 24 | 25 | [HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Run] 26 | "startup"="C:\\StartupConfig\\Startup.bat" 27 | "@ 28 | $startupRegContent | Set-Content c:\StartupConfig\EnableStartup.reg 29 | $result = cmd /c regedit.exe /S c:\StartupConfig\EnableStartup.reg 2`>`&1 30 | $result = "Startup config complete " + $result 31 | Write-Output $result 32 | 33 | c:\StartupConfig\Startup.bat 34 | -------------------------------------------------------------------------------- /scripts/write_version.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | pushd `dirname $0` > /dev/null 4 | version_dir="../artifacts/" 5 | version_filepath="../artifacts/version.json" 6 | if [ ! -d "$version_dir" ]; then 7 | mkdir $version_dir 8 | fi 9 | version_txt=`git log -n1 --format='{"sha" : "%h"}'` 10 | touch $version_filepath 11 | echo "$version_txt" > $version_filepath 12 | pushd $version_dir > /dev/null 13 | echo $version_txt written to $PWD 14 | popd > /dev/null 15 | popd > /dev/null -------------------------------------------------------------------------------- /setup.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var path = require('path'); 4 | var util = require('util'); 5 | var os = require('os'); 6 | var minimist = require('minimist'); 7 | 8 | function cli(args) { 9 | args = minimist(args, { 10 | string: ['hostname', 'username', 'password'], 11 | alias: { 12 | hostname: ['h', 'host'], 13 | port: 'p', 14 | clientid: 'id', 15 | username: 'u', 16 | password: 'P', 17 | }, 18 | default: { 19 | clientid: os.hostname().toLowerCase(), 20 | port: 1883 21 | } 22 | }); 23 | 24 | if (!args.host || !args.username || !args.password) { 25 | console.log("Usage : \r" + "setup -h {broker} [-p {port}] -u {username} -P {password} [--id clientid] ") 26 | return; 27 | } 28 | 29 | var config = {}; 30 | config.broker = { 31 | host: args.host, 32 | port: args.port, 33 | username: args.username, 34 | password: args.password, 35 | }; 36 | 37 | 38 | var id = (args.clientid && typeof(args.clientid) == "string")? args.clientid : os.hostname().toLowerCase(); 39 | console.log("[ClientID]:" + id); 40 | config.clientid = id; 41 | 42 | var objstr = JSON.stringify(config, null, '\t'); 43 | var configStr = util.format("var _creds = %s; \r\nif(typeof module != 'undefined') { \n\tmodule.exports = _creds; \n} \r\n", objstr) 44 | var fs = require('fs'); 45 | var filename = path.resolve(__dirname, "./client/_creds.js"); 46 | fs.writeFile(filename, configStr, function (err) { 47 | if (err) { 48 | return console.log(err); 49 | } 50 | 51 | console.log("Configuration written to " + filename); 52 | }); 53 | } 54 | 55 | if (require.main === module) { 56 | cli(process.argv.slice(2)); 57 | } -------------------------------------------------------------------------------- /tools/pull: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | pushd `dirname $0` 4 | 5 | if [ -z "$1" ]; then 6 | echo "No resource url specified." ; 7 | exit 1; 8 | fi 9 | 10 | if [ -z "$2" ]; then 11 | echo "No Target directory specified" 12 | exit 1 13 | fi 14 | 15 | echo "Target Output : $2" 16 | outDir="$2" 17 | 18 | #Create the output directory for download 19 | rm -rf $outDir || true 20 | 21 | mkdir $outDir 22 | pushd $outDir 23 | 24 | curl $1 | tar -zxv >/dev/null 25 | 26 | #Navigate back 27 | popd >/dev/null 28 | 29 | popd >/dev/null --------------------------------------------------------------------------------