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'
30 # Helper function to indent a text string
32 def indent(lines, amount, fillchar=' '):
33 padding = amount * fillchar
34 return padding + ('\n'+padding).join(lines.split('\n'))
38 # FIXME: Right now, this is really coded like a shell script, as one big
39 # function executed in sequence. This should be broken down into multiple
44 # Get our default interface IP address. This will become the default
45 # value for the "NFS Server IP" option.
47 gws = netifaces.gateways()
48 addrs = netifaces.ifaddresses(gws['default'][netifaces.AF_INET][1])
49 default_addr = addrs[netifaces.AF_INET][0]['addr']
52 # Verify CLI parameters and try to download our VPP image into a temporary
55 parser = argparse.ArgumentParser()
56 parser.add_argument("topology", help="the base topology to be started")
57 parser.add_argument("packages", help="Path to the VPP .deb(s) or .rpm(s) " +
58 "that is/are to be installed", nargs='+')
59 parser.add_argument("-c", "--copy", help="Copy the VPP packages, " +
60 "leaving the originals in place. Default is to " +
61 "move them.", action='store_true')
62 parser.add_argument("-k", "--keep", help="Keep (do not delete) the " +
63 "simulation in case of error", action='store_true')
64 parser.add_argument("-v", "--verbosity", action="count", default=0)
65 parser.add_argument("-nip", "--nfs-server-ip", help="NFS server (our) IP " +
66 "default is derived from routing table: " +
67 "{}".format(default_addr), default=default_addr)
68 parser.add_argument("-ns", "--nfs-scratch-directory",
69 help="Server location for NFS scratch diretory",
70 default="/nfs/scratch")
71 parser.add_argument("-nc", "--nfs-common-directory",
72 help="Server location for NFS common (read-only) " +
73 "directory", default="/nfs/common")
74 parser.add_argument("-wc", "--wait-count",
75 help="number of intervals to wait for simulation to " +
76 "be ready", type=int, default=24)
77 parser.add_argument("-wt", "--wait-time",
78 help="length of a single interval to wait for " +
79 "simulation to be ready", type=int, default=5)
80 parser.add_argument("-vip", "--virl-ip",
81 help="VIRL IP and Port (e.g. 127.0.0.1:19399)",
82 default="127.0.0.1:19399")
83 parser.add_argument("-u", "--username", help="VIRL username",
85 parser.add_argument("-p", "--password", help="VIRL password",
87 parser.add_argument("-su", "--ssh-user", help="SSH username",
89 parser.add_argument("-spr", "--ssh-privkey", help="SSH private keyfile",
90 default="/home/jenkins-in/.ssh/id_rsa_virl")
91 parser.add_argument("-spu", "--ssh-pubkey", help="SSH public keyfile",
92 default="/home/jenkins-in/.ssh/id_rsa_virl.pub")
93 parser.add_argument("-r", "--release", help="VM disk image/release " +
94 "(ex. \"csit-ubuntu-16.04.1_2016-12-19_1.6\")",
95 default="csit-ubuntu-16.04.1_2016-12-19_1.6")
96 parser.add_argument("--topology-directory", help="Topology directory",
97 default="/home/jenkins-in/testcase-infra/topologies")
99 args = parser.parse_args()
102 # Check if topology and template exist
104 if args.verbosity >= 2:
105 print "DEBUG: Running with topology {}".format(args.topology)
107 topology_virl_filename = os.path.join(args.topology_directory,
108 args.topology + ".virl")
109 topology_yaml_filename = os.path.join(args.topology_directory,
110 args.topology + ".yaml")
112 if not os.path.isfile(topology_virl_filename):
113 print "ERROR: Topology VIRL file {} does not exist".\
114 format(topology_virl_filename)
116 if not os.path.isfile(topology_yaml_filename):
117 print "ERROR: Topology YAML file {} does not exist".\
118 format(topology_yaml_filename)
122 # Check if VPP package exists
124 for package in args.packages:
125 if args.verbosity >= 2:
126 print "DEBUG: Checking if file {} exists".format(package)
127 if not os.path.isfile(package):
128 print "ERROR: Debian package {} does not exist.".format(package)
132 # Start VIRL topology
134 if args.verbosity >= 1:
135 print "DEBUG: Starting VIRL topology"
136 temp_handle, temp_topology = tempfile.mkstemp()
137 with open(args.ssh_pubkey, 'r') as pubkey_file:
138 pub_key = pubkey_file.read().replace('\n', '')
139 with open(temp_topology, 'w') as new_file, \
140 open(topology_virl_filename, 'r') as old_file:
141 for line in old_file:
142 line = line.replace(" - VIRL-USER-SSH-PUBLIC-KEY", " - "+pub_key)
143 line = line.replace("$$NFS_SERVER_SCRATCH$$",
144 args.nfs_server_ip+":"+args.nfs_scratch_directory)
145 line = line.replace("$$NFS_SERVER_COMMON$$",
146 args.nfs_server_ip+":"+args.nfs_common_directory)
147 line = line.replace("$$VM_IMAGE$$", "server-"+args.release)
149 os.close(temp_handle)
152 new_file = open(temp_topology, 'rb')
153 headers = {'Content-Type': 'text/xml'}
154 req = requests.post('http://' + args.virl_ip + '/simengine/rest/launch',
156 auth=(args.username, args.password), data=new_file)
157 if args.verbosity >= 2:
158 print "DEBUG: - Response Code {}".format(req.status_code)
162 print "ERROR: Launching VIRL simulation - received invalid response"
164 os.remove(temp_topology)
167 if req.status_code != 200:
168 print "ERROR: Launching VIRL simulation - received status other " + \
170 print "Status was: {} \n".format(req.status_code)
171 print "Response content was: "
173 os.remove(temp_topology)
176 # If we got here, we had a good response. The response content is the
178 session_id = req.content
181 # Create simulation scratch directory. Move topology file into that
182 # directory. Copy or move debian packages into that directory.
184 scratch_directory = os.path.join(args.nfs_scratch_directory, session_id)
185 os.mkdir(scratch_directory)
186 shutil.move(temp_topology, os.path.join(scratch_directory,
187 "virl_topology.virl"))
188 os.mkdir(os.path.join(scratch_directory, "vpp"))
189 for package in args.packages:
191 shutil.copy(package, os.path.join(scratch_directory, "vpp",
192 os.path.basename(package)))
194 shutil.move(package, os.path.join(scratch_directory, "vpp",
195 os.path.basename(package)))
198 # Wait for simulation to become active
200 if args.verbosity >= 1:
201 print "DEBUG: Waiting for simulation to become active"
203 sim_is_started = False
206 count = args.wait_count
207 while (count > 0) and not sim_is_started:
208 time.sleep(args.wait_time)
211 req = requests.get('http://' + args.virl_ip + '/simengine/rest/nodes/' +
212 session_id, auth=(args.username, args.password))
218 # Flush the node list every time, keep the last one
221 # Hosts are the keys of the inner dictionary
222 for key in data[session_id].keys():
223 if data[session_id][key]['management-proxy'] == "self":
227 if data[session_id][key]['state'] == "ACTIVE":
229 if args.verbosity >= 2:
230 print "DEBUG: - Attempt {} out of {}, total {} hosts, {} active".\
231 format(args.wait_count-count, args.wait_count, total, active)
233 sim_is_started = True
235 if not sim_is_started:
236 print "ERROR: Simulation started OK but devices never changed to " + \
238 print "Last VIRL response:"
241 shutil.rmtree(scratch_directory)
242 req = requests.get('http://' + args.virl_ip +
243 '/simengine/rest/stop/' + session_id,
244 auth=(args.username, args.password))
246 if args.verbosity >= 2:
247 print "DEBUG: Nodes: " + ", ".join(nodelist)
250 # Fetch simulation's IPs and create files
251 # (ansible hosts file, topology YAML file)
253 req = requests.get('http://' + args.virl_ip +
254 '/simengine/rest/interfaces/' + session_id +
255 '?fetch-state=1', auth=(args.username, args.password))
258 # Populate node addresses
262 nodetype = re.split('[0-9]', key)[0]
263 if not nodetype in nodeaddrs:
264 nodeaddrs[nodetype] = {}
265 nodeaddrs[nodetype][key] = re.split('\\/', \
266 data[session_id][key]['management']['ip-address'])[0]
267 if args.verbosity >= 2:
268 print "DEBUG: Node {} is of type {} and has management IP {}".\
269 format(key, nodetype, nodeaddrs[nodetype][key])
272 for key2 in data[session_id][key]:
273 topology[key]["nic-"+key2] = data[session_id][key][key2]
274 if 'ip-address' in topology[key]["nic-"+key2]:
275 if topology[key]["nic-"+key2]['ip-address'] is not None:
276 topology[key]["nic-"+key2]['ip-addr'] = re.split('\\/', \
277 topology[key]["nic-"+key2]['ip-address'])[0]
280 ansiblehosts = open(os.path.join(scratch_directory, 'ansible-hosts'), 'w')
281 for key1 in nodeaddrs:
282 ansiblehosts.write("[{}]\n".format(key1))
283 for key2 in nodeaddrs[key1]:
284 ansiblehosts.write("{} hostname={}\n".format(nodeaddrs[key1][key2],
288 # Process topology YAML template
289 with open(args.ssh_privkey, 'r') as privkey_file:
290 priv_key = indent(privkey_file.read(), 6)
292 with open(os.path.join(scratch_directory, "topology.yaml"), 'w') as \
293 new_file, open(topology_yaml_filename, 'r') as old_file:
294 for line in old_file:
295 new_file.write(line.format(priv_key=priv_key, topology=topology))
298 # Wait for hosts to become reachable over SSH
300 if args.verbosity >= 1:
301 print "DEBUG: Waiting for hosts to become reachable using SSH"
304 count = args.wait_count
305 while (count > 0) and missing != 0:
306 time.sleep(args.wait_time)
311 if not os.path.exists(os.path.join(scratch_directory, key)):
313 if args.verbosity >= 2:
314 print "DEBUG: - Attempt {} out of {}, waiting for {} hosts".\
315 format(args.wait_count-count, args.wait_count, missing)
318 print "ERROR: Simulation started OK but {} hosts ".format(missing) + \
319 "never mounted their NFS directory"
321 shutil.rmtree(scratch_directory)
322 req = requests.get('http://' + args.virl_ip +
323 '/simengine/rest/stop/' + session_id,
324 auth=(args.username, args.password))
329 if args.verbosity >= 1:
330 print "DEBUG: Uprading VPP"
332 for key1 in nodeaddrs:
334 for key2 in nodeaddrs[key1]:
335 ipaddr = nodeaddrs[key1][key2]
336 if args.verbosity >= 2:
337 print "DEBUG: Upgrading VPP on node {}".format(ipaddr)
338 paramiko.util.log_to_file(os.path.join(scratch_directory,
340 client = paramiko.SSHClient()
341 client.load_system_host_keys()
342 client.load_host_keys("/dev/null")
343 client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
344 client.connect(ipaddr, username=args.ssh_user,
345 key_filename=args.ssh_privkey)
346 if 'centos' in args.topology:
347 if args.verbosity >= 1:
348 print "DEBUG: Installing RPM packages"
349 vpp_install_command = 'sudo rpm -ivh /scratch/vpp/*.rpm'
350 elif 'trusty' in args.topology or 'xenial' in args.topology:
351 if args.verbosity >= 1:
352 print "DEBUG: Installing DEB packages"
353 vpp_install_command = 'sudo dpkg -i --force-all ' \
356 print "ERROR: Unsupported OS requested: {}"\
357 .format(args.topology)
358 vpp_install_command = ''
359 stdin, stdout, stderr = \
360 client.exec_command(vpp_install_command)
361 c_stdout = stdout.read()
362 c_stderr = stderr.read()
363 if args.verbosity >= 2:
364 print "DEBUG: Command output was:"
366 print "DEBUG: Command stderr was:"
370 # Write a file with timestamp to scratch directory. We can use this to track
371 # how long a simulation has been running.
373 with open(os.path.join(scratch_directory, 'start_time'), 'a') as \
375 timestampfile.write('{}\n'.format(int(time.time())))
380 if args.verbosity >= 1:
381 print "SESSION ID: {}".format(session_id)
383 print "{}".format(session_id)
385 if __name__ == "__main__":