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')
46 parser.add_argument("--distribution", default="",
47 help="the absolute path of the distribution on the local "
48 "host that needs to be deployed. (Must contain "
49 "version in the form: \"<#>.<#>.<#>-<name>\", e.g. "
52 parser.add_argument("--rootdir", default="/root",
53 help="the root directory on the remote host where the "
54 "distribution is to be deployed",
56 parser.add_argument("--hosts", default="", help="a comma separated list of "
57 "host names or ip addresses",
59 parser.add_argument("--clean", action="store_true", default=False,
60 help="clean the deployment on the remote host")
61 parser.add_argument("--template", default="openflow",
62 help="the name of the template to be used. "
63 "This name should match a folder in the templates "
65 parser.add_argument("--rf", default=3, type=int,
66 help="replication factor. This is the number of replicas "
67 "that should be created for each shard.")
68 parser.add_argument("--user", default="root", help="the SSH username for the "
70 parser.add_argument("--password", default="Ecp123",
71 help="the SSH password for the remote host(s)")
72 args = parser.parse_args()
76 # The TemplateRenderer provides methods to render a template
78 class TemplateRenderer:
79 def __init__(self, template):
80 self.cwd = os.getcwd()
81 self.template_root = self.cwd + "/templates/" + template + "/"
83 def render(self, template_path, output_path, variables=None):
87 if os.path.exists(self.template_root + template_path) is False:
90 with open(self.template_root + template_path, "r") as myfile:
93 parsed = pystache.parse(u"%(data)s" % locals())
94 renderer = pystache.Renderer()
96 output = renderer.render(parsed, variables)
98 with open(os.getcwd() + "/temp/" + output_path, "w") as myfile:
100 return os.getcwd() + "/temp/" + output_path
104 # The array_str method takes an array of strings and formats it into a
105 # string such that it can be used in an akka configuration file
109 for x in range(0, len(arr)):
110 s = s + '"' + arr[x] + '"'
111 if x < (len(arr) - 1):
118 # The Deployer deploys the controller to one host and configures it
121 def __init__(self, host, member_no, template, user, password, rootdir,
122 distribution, dir_name, hosts, ds_seed_nodes, rpc_seed_nodes,
123 replicas, clean=False):
125 self.member_no = member_no
126 self.template = template
128 self.password = password
129 self.rootdir = rootdir
131 self.distribution = distribution
132 self.dir_name = dir_name
134 self.ds_seed_nodes = ds_seed_nodes
135 self.rpc_seed_nodes = rpc_seed_nodes
136 self.replicas = replicas
138 # Connect to the remote host and start doing operations
139 self.remote = RemoteHost(self.host, self.user, self.password,
142 def kill_controller(self):
143 self.remote.copy_file("kill_controller.sh", self.rootdir + "/")
144 self.remote.exec_cmd(self.rootdir + "/kill_controller.sh")
147 # Determine distribution version
149 = os.path.splitext(os.path.basename(self.distribution))[0]
150 distribution_ver = re.search('(\d+\.\d+\.\d+-\w+\Z)|'
151 '(\d+\.\d+\.\d+-\w+)(-RC\d+\Z)|'
152 '(\d+\.\d+\.\d+-\w+)(-RC\d+(\.\d+)\Z)|'
153 '(\d+\.\d+\.\d+-\w+)(-SR\d+\Z)|'
154 '(\d+\.\d+\.\d+-\w+)(-SR\d+(\.\d+)\Z)',
155 distribution_name) # noqa
157 if distribution_ver is None:
158 print distribution_name + " is not a valid distribution version." \
159 " (Must contain version in the form: " \
160 "\"<#>.<#>.<#>-<name>\" or \"<#>.<#>." \
161 "<#>-<name>-SR<#>\" or \"<#>.<#>.<#>" \
162 "-<name>-RC<#>\", e.g. 0.2.0-SNAPSHOT)"
164 distribution_ver = distribution_ver.group()
166 # Render all the templates
167 renderer = TemplateRenderer(self.template)
168 akka_conf = renderer.render(
169 "akka.conf.template", "akka.conf",
172 "MEMBER_NAME": "member-" + str(self.member_no),
173 "DS_SEED_NODES": array_str(self.ds_seed_nodes),
174 "RPC_SEED_NODES": array_str(self.rpc_seed_nodes)
176 module_shards_conf = renderer.render("module-shards.conf.template",
177 "module-shards.conf",
179 modules_conf = renderer.render("modules.conf.template",
182 renderer.render("org.apache.karaf.features.cfg.template",
183 "org.apache.karaf.features.cfg",
184 {"ODL_DISTRIBUTION": distribution_ver})
185 jolokia_xml = renderer.render("jolokia.xml.template", "jolokia.xml")
187 renderer.render("org.apache.karaf.management.cfg.template",
188 "org.apache.karaf.management.cfg",
192 "org.opendaylight.controller.cluster.datastore.cfg.template",
193 "org.opendaylight.controller.cluster.datastore.cfg")
195 # Delete all the sub-directories under the deploy directory if
196 # the --clean flag is used
197 if self.clean is True:
198 self.remote.exec_cmd("rm -rf " + self.rootdir + "/deploy/*")
200 # Create the deployment directory
201 self.remote.mkdir(self.dir_name)
203 # Clean the m2 repository
204 self.remote.exec_cmd("rm -rf " + self.rootdir + "/.m2/repository")
206 # Copy the distribution to the host and unzip it
207 odl_file_path = self.dir_name + "/odl.zip"
208 self.remote.copy_file(self.distribution, odl_file_path)
209 self.remote.exec_cmd("unzip -o " + odl_file_path + " -d " +
212 # Rename the distribution directory to odl
213 self.remote.exec_cmd("mv " + self.dir_name + "/" +
214 distribution_name + " " + self.dir_name + "/odl")
216 # Copy all the generated files to the server
217 self.remote.mkdir(self.dir_name +
218 "/odl/configuration/initial")
219 self.remote.copy_file(akka_conf, self.dir_name +
220 "/odl/configuration/initial/")
221 self.remote.copy_file(module_shards_conf, self.dir_name +
222 "/odl/configuration/initial/")
223 self.remote.copy_file(modules_conf, self.dir_name +
224 "/odl/configuration/initial/")
225 self.remote.copy_file(features_cfg, self.dir_name +
227 self.remote.copy_file(jolokia_xml, self.dir_name +
229 self.remote.copy_file(management_cfg, self.dir_name +
232 if datastore_cfg is not None:
233 self.remote.copy_file(datastore_cfg, self.dir_name + "/odl/etc/")
236 self.remote.exec_cmd("ln -sfn " + self.dir_name + " " +
237 args.rootdir + "/deploy/current")
240 self.remote.start_controller(self.dir_name)
244 # Validate some input
245 if os.path.exists(args.distribution) is False:
246 print args.distribution + " is not a valid file"
249 if os.path.exists(os.getcwd() + "/templates/" + args.template) is False:
250 print args.template + " is not a valid template"
252 # Prepare some 'global' variables
253 hosts = args.hosts.split(",")
254 time_stamp = time.time()
255 dir_name = args.rootdir + "/deploy/" + str(time_stamp)
262 for x in range(0, len(hosts)):
263 ds_seed_nodes.append("akka.tcp://opendaylight-cluster-data@" +
265 rpc_seed_nodes.append("akka.tcp://odl-cluster-rpc@" +
267 all_replicas.append("member-" + str(x + 1))
269 for x in range(0, 10):
270 if len(all_replicas) > args.rf:
271 replicas["REPLICAS_" + str(x + 1)] \
272 = array_str(random.sample(all_replicas, args.rf))
274 replicas["REPLICAS_" + str(x + 1)] = array_str(all_replicas)
278 for x in range(0, len(hosts)):
279 deployers.append(Deployer(hosts[x], x + 1, args.template, args.user,
280 args.password, args.rootdir,
281 args.distribution, dir_name, hosts,
282 ds_seed_nodes, rpc_seed_nodes, replicas,
285 for x in range(0, len(hosts)):
286 deployers[x].kill_controller()
288 for x in range(0, len(hosts)):
289 deployers[x].deploy()