1 #!/usr/local/bin/python
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 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.
21 # Here are the things it will do,
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
30 # -------------------------------------------------------------------------------------------------------------
33 from SSHLibrary import SSHLibrary
40 from remote_host import RemoteHost
42 parser = argparse.ArgumentParser(description='Cluster Deployer')
43 parser.add_argument("--distribution", default="", help="the absolute path of the distribution on the local host that needs to be deployed. (Must contain version in the form: \"<#>.<#>.<#>-<name>\", e.g. 0.2.0-SNAPSHOT)", required=True)
44 parser.add_argument("--rootdir", default="/root", help="the root directory on the remote host where the distribution is to be deployed", required=True)
45 parser.add_argument("--hosts", default="", help="a comma separated list of host names or ip addresses", required=True)
46 parser.add_argument("--clean", action="store_true", default=False, help="clean the deployment on the remote host")
47 parser.add_argument("--template", default="openflow", help="the name of the template to be used. This name should match a folder in the templates directory.")
48 parser.add_argument("--rf", default=3, type=int, help="replication factor. This is the number of replicas that should be created for each shard.")
49 parser.add_argument("--user", default="root", help="the SSH username for the remote host(s)")
50 parser.add_argument("--password", default="Ecp123", help="the SSH password for the remote host(s)")
51 args = parser.parse_args()
56 # The TemplateRenderer provides methods to render a template
58 class TemplateRenderer:
59 def __init__(self, template):
60 self.cwd = os.getcwd()
61 self.template_root = self.cwd + "/templates/" + template + "/"
63 def render(self, template_path, output_path, variables={}):
64 with open (self.template_root + template_path, "r") as myfile:
67 parsed = pystache.parse(u"%(data)s" % locals())
68 renderer = pystache.Renderer()
70 output = renderer.render(parsed, variables)
72 with open (os.getcwd() + "/temp/" + output_path, "w") as myfile:
74 return os.getcwd() + "/temp/" + output_path
77 # The array_str method takes an array of strings and formats it into a string such that
78 # it can be used in an akka configuration file
82 for x in range(0, len(arr)):
83 s = s + '"' + arr[x] + '"'
84 if x < (len(arr) - 1):
90 # The Deployer deploys the controller to one host and configures it
93 def __init__(self, host, member_no, template, user, password, rootdir, distribution,
94 dir_name, hosts, ds_seed_nodes, rpc_seed_nodes, replicas, clean=False):
96 self.member_no = member_no
97 self.template = template
99 self.password = password
100 self.rootdir = rootdir
102 self.distribution = distribution
103 self.dir_name = dir_name
105 self.ds_seed_nodes = ds_seed_nodes
106 self.rpc_seed_nodes = rpc_seed_nodes
107 self.replicas = replicas
110 # Determine distribution version
111 distribution_name = os.path.splitext(os.path.basename(self.distribution))[0]
112 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)
114 if distribution_ver is None:
115 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)"
117 distribution_ver = distribution_ver.group()
119 # Render all the templates
120 renderer = TemplateRenderer(self.template)
121 akka_conf = renderer.render("akka.conf.template", "akka.conf",
124 "MEMBER_NAME" : "member-" + str(self.member_no),
125 "DS_SEED_NODES" : array_str(self.ds_seed_nodes),
126 "RPC_SEED_NODES" : array_str(self.rpc_seed_nodes)
129 module_shards_conf = renderer.render("module-shards.conf.template", "module-shards.conf", self.replicas)
130 modules_conf = renderer.render("modules.conf.template", "modules.conf")
131 features_cfg = renderer.render("org.apache.karaf.features.cfg.template", "org.apache.karaf.features.cfg", {"ODL_DISTRIBUTION" : distribution_ver})
132 jolokia_xml = renderer.render("jolokia.xml.template", "jolokia.xml")
133 management_cfg = renderer.render("org.apache.karaf.management.cfg.template", "org.apache.karaf.management.cfg", {"HOST" : self.host})
135 # Connect to the remote host and start doing operations
136 remote = RemoteHost(self.host, self.user, self.password, self.rootdir)
137 remote.mkdir(self.dir_name)
139 # Delete all the sub-directories under the deploy directory if the --clean flag is used
140 if(self.clean == True):
141 remote.exec_cmd("rm -rf " + self.rootdir + "/deploy/*")
143 # Clean the m2 repository
144 remote.exec_cmd("rm -rf " + self.rootdir + "/.m2/repository")
146 # Kill the controller if it's running
147 remote.kill_controller()
149 # Copy the distribution to the host and unzip it
150 odl_file_path = self.dir_name + "/odl.zip"
151 remote.copy_file(self.distribution, odl_file_path)
152 remote.exec_cmd("unzip " + odl_file_path + " -d " + self.dir_name + "/")
154 # Rename the distribution directory to odl
155 remote.exec_cmd("mv " + self.dir_name + "/" + distribution_name + " " + self.dir_name + "/odl")
157 # Copy all the generated files to the server
158 remote.mkdir(self.dir_name + "/odl/configuration/initial")
159 remote.copy_file(akka_conf, self.dir_name + "/odl/configuration/initial/")
160 remote.copy_file(module_shards_conf, self.dir_name + "/odl/configuration/initial/")
161 remote.copy_file(modules_conf, self.dir_name + "/odl/configuration/initial/")
162 remote.copy_file(features_cfg, self.dir_name + "/odl/etc/")
163 remote.copy_file(jolokia_xml, self.dir_name + "/odl/deploy/")
164 remote.copy_file(management_cfg, self.dir_name + "/odl/etc/")
167 remote.exec_cmd("ln -sfn " + self.dir_name + " " + args.rootdir + "/deploy/current")
170 remote.start_controller(self.dir_name)
174 # Validate some input
175 if os.path.exists(args.distribution) == False:
176 print args.distribution + " is not a valid file"
179 if os.path.exists(os.getcwd() + "/templates/" + args.template) == False:
180 print args.template + " is not a valid template"
182 # Prepare some 'global' variables
183 hosts = args.hosts.split(",")
184 time_stamp = time.time()
185 dir_name = args.rootdir + "/deploy/" + str(time_stamp)
186 distribution_name = os.path.splitext(os.path.basename(args.distribution))[0]
193 for x in range(0, len(hosts)):
194 ds_seed_nodes.append("akka.tcp://opendaylight-cluster-data@" + hosts[x] + ":2550")
195 rpc_seed_nodes.append("akka.tcp://odl-cluster-rpc@" + hosts[x] + ":2551")
196 all_replicas.append("member-" + str(x+1))
199 for x in range(0, 10):
200 if len(all_replicas) > args.rf:
201 replicas["REPLICAS_" + str(x+1)] = array_str(random.sample(all_replicas, args.rf))
203 replicas["REPLICAS_" + str(x+1)] = array_str(all_replicas)
205 for x in range(0, len(hosts)):
206 Deployer(hosts[x], x+1, args.template, args.user, args.password, args.rootdir, args.distribution, dir_name, hosts, ds_seed_nodes, rpc_seed_nodes, replicas, args.clean).deploy()