3 # Copyright (c) 2016 Cisco and/or its affiliates.
4 # Licensed under the Apache License, Version 2.0 (the "License");
5 # you may not use this file except in compliance with the License.
6 # You may obtain a copy of the License at:
8 # http://www.apache.org/licenses/LICENSE-2.0
10 # Unless required by applicable law or agreed to in writing, software
11 # distributed under the License is distributed on an "AS IS" BASIS,
12 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 # See the License for the specific language governing permissions and
14 # limitations under the License.
16 __author__ = 'ckoester@cisco.com'
29 # Helper function to indent a text string
31 def indent(lines, amount, fillchar=' '):
32 padding = amount * fillchar
33 return padding + ('\n'+padding).join(lines.split('\n'))
37 # FIXME: Right now, this is really coded like a shell script, as one big
38 # function executed in sequence. This should be broken down into multiple
43 # Verify CLI parameters and try to download our VPP image into a temporary
46 parser = argparse.ArgumentParser()
47 parser.add_argument("topology", help="the base topology to be started")
48 parser.add_argument("packages", help="Path to the VPP .deb(s) that " +
49 "is/are to be installed", nargs='+')
50 parser.add_argument("-c", "--copy", help="Copy the .deb packages, " +
51 "leaving the originals in place. Default is to " +
52 "move them.", action='store_true')
53 parser.add_argument("-k", "--keep", help="Keep (do not delete) the " +
54 "simulation in case of error", action='store_true')
55 parser.add_argument("-v", "--verbosity", action="count", default=0)
56 # FIXME: THe default value for the following line should not be a hardcoded
57 # address. We should determine it dynamically (e.g. IP address of first
58 # interface or whichever interface is tied to the flat network)
59 parser.add_argument("-nip", "--nfs-server-ip", help="NFS server (our) IP",
60 default="10.30.51.28")
61 parser.add_argument("-ns", "--nfs-scratch-directory",
62 help="Server location for NFS scratch diretory",
63 default="/nfs/scratch")
64 parser.add_argument("-nc", "--nfs-common-directory",
65 help="Server location for NFS common (read-only) " +
66 "directory", default="/nfs/common")
67 parser.add_argument("-wc", "--wait-count",
68 help="number of intervals to wait for simulation to " +
69 "be ready", type=int, default=12)
70 parser.add_argument("-wt", "--wait-time",
71 help="length of a single interval to wait for " +
72 "simulation to be ready", type=int, default=5)
73 parser.add_argument("-vip", "--virl-ip",
74 help="VIRL IP and Port (e.g. 127.0.0.1:19399)",
75 default="127.0.0.1:19399")
76 parser.add_argument("-u", "--username", help="VIRL username",
78 parser.add_argument("-p", "--password", help="VIRL password",
80 parser.add_argument("-su", "--ssh-user", help="SSH username",
82 parser.add_argument("-spr", "--ssh-privkey", help="SSH private keyfile",
83 default="/home/jenkins-in/.ssh/id_rsa_virl")
84 parser.add_argument("-spu", "--ssh-pubkey", help="SSH public keyfile",
85 default="/home/jenkins-in/.ssh/id_rsa_virl.pub")
86 parser.add_argument("--topology-directory", help="Topology directory",
87 default="/home/jenkins-in/testcase-infra/topologies")
89 args = parser.parse_args()
92 # Check if topology and template exist
94 if args.verbosity >= 2:
95 print "DEBUG: Running with topology {}".format(args.topology)
97 topology_virl_filename = os.path.join(args.topology_directory,
98 args.topology + ".virl")
99 topology_yaml_filename = os.path.join(args.topology_directory,
100 args.topology + ".yaml")
102 if not os.path.isfile(topology_virl_filename):
103 print "ERROR: Topology VIRL file {} does not exist".\
104 format(topology_virl_filename)
106 if not os.path.isfile(topology_yaml_filename):
107 print "ERROR: Topology YAML file {} does not exist".\
108 format(topology_yaml_filename)
112 # Check if VPP package exists
114 for package in args.packages:
115 if args.verbosity >= 2:
116 print "DEBUG: Checking if file {} exists".format(package)
117 if not os.path.isfile(package):
118 print "ERROR: Debian package {} does not exist.".format(package)
122 # Start VIRL topology
124 if args.verbosity >= 1:
125 print "DEBUG: Starting VIRL topology"
126 temp_handle, temp_topology = tempfile.mkstemp()
127 with open(args.ssh_pubkey, 'r') as pubkey_file:
128 pub_key = pubkey_file.read().replace('\n', '')
129 with open(temp_topology, 'w') as new_file, \
130 open(topology_virl_filename, 'r') as old_file:
131 for line in old_file:
132 line = line.replace(" - VIRL-USER-SSH-PUBLIC-KEY", " - "+pub_key)
133 line = line.replace("$$NFS_SERVER_SCRATCH$$", \
134 args.nfs_server_ip+":"+args.nfs_scratch_directory)
135 line = line.replace("$$NFS_SERVER_COMMON$$", \
136 args.nfs_server_ip+":"+args.nfs_common_directory)
138 os.close(temp_handle)
141 new_file = open(temp_topology, 'rb')
142 headers = {'Content-Type': 'text/xml'}
143 req = requests.post('http://' + args.virl_ip + '/simengine/rest/launch',
145 auth=(args.username, args.password), data=new_file)
146 if args.verbosity >= 2:
147 print "DEBUG: - Response Code {}".format(req.status_code)
151 print "ERROR: Launching VIRL simulation - received invalid response"
153 os.remove(temp_topology)
156 if req.status_code != 200:
157 print "ERROR: Launching VIRL simulation - received status other " + \
159 print "Status was: {} \n".format(req.status_code)
160 print "Response content was: "
162 os.remove(temp_topology)
165 # If we got here, we had a good response. The response content is the
167 session_id = req.content
170 # Create simulation scratch directory. Move topology file into that
171 # directory. Copy or move debian packages into that directory.
173 scratch_directory = os.path.join(args.nfs_scratch_directory, session_id)
174 os.mkdir(scratch_directory)
175 shutil.move(temp_topology, os.path.join(scratch_directory,
176 "virl_topology.virl"))
177 os.mkdir(os.path.join(scratch_directory, "vpp"))
178 for package in args.packages:
180 shutil.copy(package, os.path.join(scratch_directory, "vpp",
181 os.path.basename(package)))
183 shutil.move(package, os.path.join(scratch_directory, "vpp",
184 os.path.basename(package)))
187 # Wait for simulation to become active
189 if args.verbosity >= 1:
190 print "DEBUG: Waiting for simulation to become active"
192 sim_is_started = False
195 count = args.wait_count
196 while (count > 0) and not sim_is_started:
197 time.sleep(args.wait_time)
200 req = requests.get('http://' + args.virl_ip + '/simengine/rest/nodes/' +
201 session_id, auth=(args.username, args.password))
207 # Flush the node list every time, keep the last one
210 # Hosts are the keys of the inner dictionary
211 for key in data[session_id].keys():
212 if data[session_id][key]['management-proxy'] == "self":
216 if data[session_id][key]['state'] == "ACTIVE":
218 if args.verbosity >= 2:
219 print "DEBUG: - Attempt {} out of {}, total {} hosts, {} active".\
220 format(args.wait_count-count, args.wait_count, total, active)
222 sim_is_started = True
224 if not sim_is_started:
225 print "ERROR: Simulation started OK but devices never changed to " + \
227 print "Last VIRL response:"
230 shutil.rmtree(scratch_directory)
231 req = requests.get('http://' + args.virl_ip +
232 '/simengine/rest/stop/' + session_id,
233 auth=(args.username, args.password))
235 if args.verbosity >= 2:
236 print "DEBUG: Nodes: " + ", ".join(nodelist)
239 # Fetch simulation's IPs and create files
240 # (ansible hosts file, topology YAML file)
242 req = requests.get('http://' + args.virl_ip +
243 '/simengine/rest/interfaces/' + session_id +
244 '?fetch-state=1', auth=(args.username, args.password))
247 # Populate node addresses
251 nodetype = re.split('[0-9]', key)[0]
252 if not nodetype in nodeaddrs:
253 nodeaddrs[nodetype] = {}
254 nodeaddrs[nodetype][key] = re.split('\\/', \
255 data[session_id][key]['management']['ip-address'])[0]
256 if args.verbosity >= 2:
257 print "DEBUG: Node {} is of type {} and has management IP {}".\
258 format(key, nodetype, nodeaddrs[nodetype][key])
261 for key2 in data[session_id][key]:
262 topology[key]["nic-"+key2] = data[session_id][key][key2]
263 if 'ip-address' in topology[key]["nic-"+key2]:
264 if topology[key]["nic-"+key2]['ip-address'] is not None:
265 topology[key]["nic-"+key2]['ip-addr'] = re.split('\\/', \
266 topology[key]["nic-"+key2]['ip-address'])[0]
269 ansiblehosts = open(os.path.join(scratch_directory, 'ansible-hosts'), 'w')
270 for key1 in nodeaddrs:
271 ansiblehosts.write("[{}]\n".format(key1))
272 for key2 in nodeaddrs[key1]:
273 ansiblehosts.write("{} hostname={}\n".format(nodeaddrs[key1][key2],
277 # Process topology YAML template
278 with open(args.ssh_privkey, 'r') as privkey_file:
279 priv_key = indent(privkey_file.read(), 6)
281 with open(os.path.join(scratch_directory, "topology.yaml"), 'w') as \
282 new_file, open(topology_yaml_filename, 'r') as old_file:
283 for line in old_file:
284 new_file.write(line.format(priv_key=priv_key, topology=topology))
287 # Wait for hosts to become reachable over SSH
289 if args.verbosity >= 1:
290 print "DEBUG: Waiting for hosts to become reachable using SSH"
293 count = args.wait_count
294 while (count > 0) and missing != 0:
295 time.sleep(args.wait_time)
300 if not os.path.exists(os.path.join(scratch_directory, key)):
302 if args.verbosity >= 2:
303 print "DEBUG: - Attempt {} out of {}, waiting for {} hosts".\
304 format(args.wait_count-count, args.wait_count, missing)
307 print "ERROR: Simulation started OK but {} hosts ".format(missing) + \
308 "never mounted their NFS directory"
310 shutil.rmtree(scratch_directory)
311 req = requests.get('http://' + args.virl_ip +
312 '/simengine/rest/stop/' + session_id,
313 auth=(args.username, args.password))
318 if args.verbosity >= 1:
319 print "DEBUG: Uprading VPP"
321 for key1 in nodeaddrs:
323 for key2 in nodeaddrs[key1]:
324 ipaddr = nodeaddrs[key1][key2]
325 if args.verbosity >= 2:
326 print "DEBUG: Upgrading VPP on node {}".format(ipaddr)
327 paramiko.util.log_to_file(os.path.join(scratch_directory,
329 client = paramiko.SSHClient()
330 client.load_system_host_keys()
331 client.load_host_keys("/dev/null")
332 client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
333 client.connect(ipaddr, username=args.ssh_user,
334 key_filename=args.ssh_privkey)
335 stdin, stdout, stderr = \
336 client.exec_command('sudo dpkg -i /scratch/vpp/*deb')
337 c_stdout = stdout.read()
338 c_stderr = stderr.read()
339 if args.verbosity >= 2:
340 print "DEBUG: Command output was:"
342 print "DEBUG: Command stderr was:"
346 # Write a file with timestamp to scratch directory. We can use this to track
347 # how long a simulation has been running.
349 with open(os.path.join(scratch_directory, 'start_time'), 'a') as \
351 timestampfile.write('{}\n'.format(int(time.time())))
356 if args.verbosity >= 1:
357 print "SESSION ID: {}".format(session_id)
359 print "{}".format(session_id)
361 if __name__ == "__main__":