3 # This script deploys a cluster
4 # -------------------------------------------
8 # - SSHLibrary (pip install robotframework-sshlibrary)
9 # - pystache (pip install pystache)
10 # - argparse (pip install argparse)
12 # The input that this script will take is as follows,
14 # - A comma separated list of ip addresses/hostnames for each host on which
15 # the distribution needs to be deployed
16 # - The replication factor to be used
17 # - The ssh username/password of the remote host(s). Note that this should be
18 # the same for each host
19 # - The name of the template to be used.
20 # Note that this template name should match the name of a template folder in
21 # the templates directory.
22 # The templates directory can be found in the same directory as this script.
24 # Here are the things it will do,
26 # - Copy over a distribution of opendaylight to the remote host
27 # - Create a timestamped directory on the remote host
28 # - Unzip the distribution to the timestamped directory
29 # - Copy over the template substituted configuration files to the appropriate
30 # location on the remote host
31 # - Create a symlink to the timestamped directory
34 # ----------------------------------------------------------------------------
43 from remote_host import RemoteHost
45 parser = argparse.ArgumentParser(description="Cluster Deployer")
49 help="the absolute path of the distribution on the local "
50 "host that needs to be deployed. (Must contain "
51 'version in the form: "<#>.<#>.<#>-<name>", e.g. '
58 help="the root directory on the remote host where the "
59 "distribution is to be deployed",
65 help="a comma separated list of " "host names or ip addresses",
72 help="clean the deployment on the remote host",
77 help="the name of the template to be used. "
78 "This name should match a folder in the templates "
85 help="replication factor. This is the number of replicas "
86 "that should be created for each shard.",
89 "--user", default="root", help="the SSH username for the " "remote host(s)"
92 "--password", default="Ecp123", help="the SSH password for the remote host(s)"
94 args = parser.parse_args()
98 # The TemplateRenderer provides methods to render a template
100 class TemplateRenderer:
101 def __init__(self, template):
102 self.cwd = os.getcwd()
103 self.template_root = self.cwd + "/templates/" + template + "/"
105 def render(self, template_path, output_path, variables=None):
106 if variables is None:
109 if os.path.exists(self.template_root + template_path) is False:
112 with open(self.template_root + template_path, "r") as myfile:
115 parsed = pystache.parse("%(data)s" % locals())
116 renderer = pystache.Renderer()
118 output = renderer.render(parsed, variables)
120 with open(os.getcwd() + "/temp/" + output_path, "w") as myfile:
122 return os.getcwd() + "/temp/" + output_path
126 # The array_str method takes an array of strings and formats it into a
127 # string such that it can be used in an akka configuration file
131 for x in range(0, len(arr)):
132 s = s + '"' + arr[x] + '"'
133 if x < (len(arr) - 1):
140 # The Deployer deploys the controller to one host and configures it
160 self.member_no = member_no
161 self.template = template
163 self.password = password
164 self.rootdir = rootdir
166 self.distribution = distribution
167 self.dir_name = dir_name
169 self.ds_seed_nodes = ds_seed_nodes
170 self.rpc_seed_nodes = rpc_seed_nodes
171 self.replicas = replicas
173 # Connect to the remote host and start doing operations
174 self.remote = RemoteHost(self.host, self.user, self.password, self.rootdir)
176 def kill_controller(self):
177 self.remote.copy_file("kill_controller.sh", self.rootdir + "/")
178 self.remote.exec_cmd(self.rootdir + "/kill_controller.sh")
181 # Determine distribution version
182 distribution_name = os.path.splitext(os.path.basename(self.distribution))[0]
183 distribution_ver = re.search(
184 r"(\d+\.\d+\.\d+-\w+\Z)|"
185 r"(\d+\.\d+\.\d+-\w+)(-RC\d+\Z)|"
186 r"(\d+\.\d+\.\d+-\w+)(-RC\d+(\.\d+)\Z)|"
187 r"(\d+\.\d+\.\d+-\w+)(-SR\d+\Z)|"
188 r"(\d+\.\d+\.\d+-\w+)(-SR\d+(\.\d+)\Z)",
192 if distribution_ver is None:
194 "%s is not a valid distribution version."
195 " (Must contain version in the form: "
196 '"<#>.<#>.<#>-<name>" or "<#>.<#>.'
197 '<#>-<name>-SR<#>" or "<#>.<#>.<#>'
198 '-<name>-RC<#>", e.g. 0.2.0-SNAPSHOT)' % distribution_name
201 distribution_ver = distribution_ver.group()
203 # Render all the templates
204 renderer = TemplateRenderer(self.template)
205 akka_conf = renderer.render(
206 "akka.conf.template",
210 "MEMBER_NAME": "member-" + str(self.member_no),
211 "DS_SEED_NODES": array_str(self.ds_seed_nodes),
212 "RPC_SEED_NODES": array_str(self.rpc_seed_nodes),
215 module_shards_conf = renderer.render(
216 "module-shards.conf.template", "module-shards.conf", self.replicas
218 modules_conf = renderer.render("modules.conf.template", "modules.conf")
219 features_cfg = renderer.render(
220 "org.apache.karaf.features.cfg.template",
221 "org.apache.karaf.features.cfg",
222 {"ODL_DISTRIBUTION": distribution_ver},
224 jolokia_xml = renderer.render("jolokia.xml.template", "jolokia.xml")
225 management_cfg = renderer.render(
226 "org.apache.karaf.management.cfg.template",
227 "org.apache.karaf.management.cfg",
230 datastore_cfg = renderer.render(
231 "org.opendaylight.controller.cluster.datastore.cfg.template",
232 "org.opendaylight.controller.cluster.datastore.cfg",
235 # Delete all the sub-directories under the deploy directory if
236 # the --clean flag is used
237 if self.clean is True:
238 self.remote.exec_cmd("rm -rf " + self.rootdir + "/deploy/*")
240 # Create the deployment directory
241 self.remote.mkdir(self.dir_name)
243 # Clean the m2 repository
244 self.remote.exec_cmd("rm -rf " + self.rootdir + "/.m2/repository")
246 # Copy the distribution to the host and unzip it
247 odl_file_path = self.dir_name + "/odl.zip"
248 self.remote.copy_file(self.distribution, odl_file_path)
249 self.remote.exec_cmd("unzip -o " + odl_file_path + " -d " + self.dir_name + "/")
251 # Rename the distribution directory to odl
252 self.remote.exec_cmd(
262 # Copy all the generated files to the server
263 self.remote.mkdir(self.dir_name + "/odl/configuration/initial")
264 self.remote.copy_file(akka_conf, self.dir_name + "/odl/configuration/initial/")
265 self.remote.copy_file(
266 module_shards_conf, self.dir_name + "/odl/configuration/initial/"
268 self.remote.copy_file(
269 modules_conf, self.dir_name + "/odl/configuration/initial/"
271 self.remote.copy_file(features_cfg, self.dir_name + "/odl/etc/")
272 self.remote.copy_file(jolokia_xml, self.dir_name + "/odl/deploy/")
273 self.remote.copy_file(management_cfg, self.dir_name + "/odl/etc/")
275 if datastore_cfg is not None:
276 self.remote.copy_file(datastore_cfg, self.dir_name + "/odl/etc/")
279 self.remote.exec_cmd(
280 "ln -sfn " + self.dir_name + " " + args.rootdir + "/deploy/current"
284 self.remote.start_controller(self.dir_name)
288 # Validate some input
289 if os.path.exists(args.distribution) is False:
290 print("%s is not a valid file" % args.distribution)
293 if os.path.exists(os.getcwd() + "/templates/" + args.template) is False:
294 print("%s is not a valid template" % args.template)
296 # Prepare some 'global' variables
297 hosts = args.hosts.split(",")
298 time_stamp = time.time()
299 dir_name = args.rootdir + "/deploy/" + str(time_stamp)
306 for x in range(0, len(hosts)):
307 ds_seed_nodes.append(
308 "akka.tcp://opendaylight-cluster-data@" + hosts[x] + ":2550"
310 rpc_seed_nodes.append("akka.tcp://odl-cluster-rpc@" + hosts[x] + ":2551")
311 all_replicas.append("member-" + str(x + 1))
313 for x in range(0, 10):
314 if len(all_replicas) > args.rf:
315 replicas["REPLICAS_" + str(x + 1)] = array_str(
316 random.sample(all_replicas, args.rf)
319 replicas["REPLICAS_" + str(x + 1)] = array_str(all_replicas)
323 for x in range(0, len(hosts)):
342 for x in range(0, len(hosts)):
343 deployers[x].kill_controller()
345 for x in range(0, len(hosts)):
346 deployers[x].deploy()