ccc69e4a3693387935ad0c4610513030dde01b5d
[integration/test.git] / test / tools / clustering / cluster-deployer / deploy.py
1 #!/usr/local/bin/python
2 #
3 # This script deploys a cluster
4 # -------------------------------------------
5 #
6 # Pre-requisites
7 # - Python 2.7
8 # - SSHLibrary (pip install robotframework-sshlibrary)
9 # - pystache (pip install pystache)
10 # - argparse (pip install argparse)
11 #
12 # The input that this script will take is as follows,
13 #
14 # - A comma separated list of ip addresses/hostnames for each host on which the distribution needs to be deployed
15 # - The replication factor to be used
16 # - The ssh username/password of the remote host(s). Note that this should be the same for each host
17 # - The name of the template to be used.
18 #   Note that this template name should match the name of a template folder in the templates directory.
19 #   The templates directory can be found in the same directory as this script.
20 #
21 # Here are the things it will do,
22 #
23 # - Copy over a distribution of opendaylight to the remote host
24 # - Create a timestamped directory on the remote host
25 # - Unzip the distribution to the timestamped directory
26 # - Copy over the template substituted configuration files to the appropriate location on the remote host
27 # - Create a symlink to the timestamped directory
28 # - Start karaf
29 #
30 # -------------------------------------------------------------------------------------------------------------
31
32 import argparse
33 import time
34 import pystache
35 import os
36 import sys
37 import random
38 import re
39 from remote_host import RemoteHost
40
41 parser = argparse.ArgumentParser(description='Cluster Deployer')
42 parser.add_argument("--distribution", default="",
43                     help="the absolute path of the distribution on the local host that needs to be deployed. "
44                     "(Must contain version in the form: \"<#>.<#>.<#>-<name>\", e.g. 0.2.0-SNAPSHOT)",
45                     required=True)
46 parser.add_argument("--rootdir", default="/root",
47                     help="the root directory on the remote host where the distribution is to be deployed",
48                     required=True)
49 parser.add_argument("--hosts", default="", help="a comma separated list of host names or ip addresses",
50                     required=True)
51 parser.add_argument("--clean", action="store_true", default=False, help="clean the deployment on the remote host")
52 parser.add_argument("--template", default="openflow",
53                     help="the name of the template to be used. "
54                     "This name should match a folder in the templates directory.")
55 parser.add_argument("--rf", default=3, type=int,
56                     help="replication factor. This is the number of replicas that should be created for each shard.")
57 parser.add_argument("--user", default="root", help="the SSH username for the remote host(s)")
58 parser.add_argument("--password", default="Ecp123", help="the SSH password for the remote host(s)")
59 args = parser.parse_args()
60
61
62 #
63 # The TemplateRenderer provides methods to render a template
64 #
65 class TemplateRenderer:
66     def __init__(self, template):
67         self.cwd = os.getcwd()
68         self.template_root = self.cwd + "/templates/" + template + "/"
69
70     def render(self, template_path, output_path, variables={}):
71         with open(self.template_root + template_path, "r") as myfile:
72             data = myfile.read()
73
74         parsed = pystache.parse(u"%(data)s" % locals())
75         renderer = pystache.Renderer()
76
77         output = renderer.render(parsed, variables)
78
79         with open(os.getcwd() + "/temp/" + output_path, "w") as myfile:
80             myfile.write(output)
81         return os.getcwd() + "/temp/" + output_path
82
83
84 #
85 # The array_str method takes an array of strings and formats it into a string such that
86 # it can be used in an akka configuration file
87 #
88 def array_str(arr):
89     s = "["
90     for x in range(0, len(arr)):
91         s = s + '"' + arr[x] + '"'
92         if x < (len(arr) - 1):
93             s = s + ","
94     s = s + "]"
95     return s
96
97
98 #
99 # The Deployer deploys the controller to one host and configures it
100 #
101 class Deployer:
102     def __init__(self, host, member_no, template, user, password, rootdir, distribution,
103                  dir_name, hosts, ds_seed_nodes, rpc_seed_nodes, replicas, clean=False):
104         self.host = host
105         self.member_no = member_no
106         self.template = template
107         self.user = user
108         self.password = password
109         self.rootdir = rootdir
110         self.clean = clean
111         self.distribution = distribution
112         self.dir_name = dir_name
113         self.hosts = hosts
114         self.ds_seed_nodes = ds_seed_nodes
115         self.rpc_seed_nodes = rpc_seed_nodes
116         self.replicas = replicas
117
118     def deploy(self):
119         # Determine distribution version
120         distribution_name = os.path.splitext(os.path.basename(self.distribution))[0]
121         distribution_ver = re.search('(\d+\.\d+\.\d+-\w+\Z)|(\d+\.\d+\.\d+-\w+)(-SR\d+\Z)|(\d+\.\d+\.\d+-\w+)(-SR\d+(\.\d+)\Z)', distribution_name)  # noqa
122
123         if distribution_ver is None:
124             print distribution_name + " is not a valid distribution version. (Must contain version in the form: \"<#>.<#>.<#>-<name>\" or \"<#>.<#>.<#>-<name>-SR<#>\" or \"<#>.<#>.<#>-<name>\", e.g. 0.2.0-SNAPSHOT)"  # noqa
125             sys.exit(1)
126         distribution_ver = distribution_ver.group()
127
128         # Render all the templates
129         renderer = TemplateRenderer(self.template)
130         akka_conf = renderer.render(
131             "akka.conf.template", "akka.conf",
132             {
133                 "HOST": self.host,
134                 "MEMBER_NAME": "member-" + str(self.member_no),
135                 "DS_SEED_NODES": array_str(self.ds_seed_nodes),
136                 "RPC_SEED_NODES": array_str(self.rpc_seed_nodes)
137             })
138         module_shards_conf = renderer.render("module-shards.conf.template", "module-shards.conf", self.replicas)
139         modules_conf = renderer.render("modules.conf.template", "modules.conf")
140         features_cfg = renderer.render("org.apache.karaf.features.cfg.template",
141                                        "org.apache.karaf.features.cfg",
142                                        {"ODL_DISTRIBUTION": distribution_ver})
143         jolokia_xml = renderer.render("jolokia.xml.template", "jolokia.xml")
144         management_cfg = renderer.render("org.apache.karaf.management.cfg.template",
145                                          "org.apache.karaf.management.cfg",
146                                          {"HOST": self.host})
147
148         # Connect to the remote host and start doing operations
149         remote = RemoteHost(self.host, self.user, self.password, self.rootdir)
150         remote.mkdir(self.dir_name)
151
152         # Delete all the sub-directories under the deploy directory if the --clean flag is used
153         if(self.clean is True):
154             remote.exec_cmd("rm -rf " + self.rootdir + "/deploy/*")
155
156         # Clean the m2 repository
157         remote.exec_cmd("rm -rf " + self.rootdir + "/.m2/repository")
158
159         # Kill the controller if it's running
160         remote.kill_controller()
161
162         # Copy the distribution to the host and unzip it
163         odl_file_path = self.dir_name + "/odl.zip"
164         remote.copy_file(self.distribution, odl_file_path)
165         remote.exec_cmd("unzip " + odl_file_path + " -d " + self.dir_name + "/")
166
167         # Rename the distribution directory to odl
168         remote.exec_cmd("mv " + self.dir_name + "/" + distribution_name + " " + self.dir_name + "/odl")
169
170         # Copy all the generated files to the server
171         remote.mkdir(self.dir_name + "/odl/configuration/initial")
172         remote.copy_file(akka_conf, self.dir_name + "/odl/configuration/initial/")
173         remote.copy_file(module_shards_conf, self.dir_name + "/odl/configuration/initial/")
174         remote.copy_file(modules_conf, self.dir_name + "/odl/configuration/initial/")
175         remote.copy_file(features_cfg, self.dir_name + "/odl/etc/")
176         remote.copy_file(jolokia_xml, self.dir_name + "/odl/deploy/")
177         remote.copy_file(management_cfg, self.dir_name + "/odl/etc/")
178
179         # Add symlink
180         remote.exec_cmd("ln -sfn " + self.dir_name + " " + args.rootdir + "/deploy/current")
181
182         # Run karaf
183         remote.start_controller(self.dir_name)
184
185
186 def main():
187     # Validate some input
188     if os.path.exists(args.distribution) is False:
189         print args.distribution + " is not a valid file"
190         sys.exit(1)
191
192     if os.path.exists(os.getcwd() + "/templates/" + args.template) is False:
193         print args.template + " is not a valid template"
194
195     # Prepare some 'global' variables
196     hosts = args.hosts.split(",")
197     time_stamp = time.time()
198     dir_name = args.rootdir + "/deploy/" + str(time_stamp)
199     distribution_name = os.path.splitext(os.path.basename(args.distribution))[0]  # noqa
200
201     ds_seed_nodes = []
202     rpc_seed_nodes = []
203     all_replicas = []
204     replicas = {}
205
206     for x in range(0, len(hosts)):
207         ds_seed_nodes.append("akka.tcp://opendaylight-cluster-data@" + hosts[x] + ":2550")
208         rpc_seed_nodes.append("akka.tcp://odl-cluster-rpc@" + hosts[x] + ":2551")
209         all_replicas.append("member-" + str(x + 1))
210
211     for x in range(0, 10):
212         if len(all_replicas) > args.rf:
213             replicas["REPLICAS_" + str(x+1)] = array_str(random.sample(all_replicas, args.rf))
214         else:
215             replicas["REPLICAS_" + str(x+1)] = array_str(all_replicas)
216
217     for x in range(0, len(hosts)):
218         Deployer(hosts[x], x + 1, args.template, args.user, args.password,
219                  args.rootdir, args.distribution, dir_name, hosts, ds_seed_nodes,
220                  rpc_seed_nodes, replicas, args.clean).deploy()
221
222 # Run the script
223 main()