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 # Commands needed to install VPP and Honeycomb from repository
31 'sudo rm /etc/apt/sources.list.d/99fd.io.list || true',
32 'echo "deb [trusted=yes] https://nexus.fd.io/content/repositories/'
33 'fd.io.master.ubuntu.trusty.main/ ./" | '
34 'sudo tee -a /etc/apt/sources.list.d/99fd.io.list',
35 'sudo apt-get -y update',
36 'sudo apt-get -y install vpp vpp-dbg vpp-dev vpp-dpdk-dev vpp-dpdk-dkms '
41 # Helper function to indent a text string
43 def indent(lines, amount, fillchar=' '):
44 padding = amount * fillchar
45 return padding + ('\n'+padding).join(lines.split('\n'))
47 def perform_remote_command(ssh_client, cmd, verbosity):
48 """This function performs a command on the specified remote host using given
49 ssh client. Depending on the level of verbosity, it prints stdout and
52 :param ssh_client: SSH client used for communication.
53 :param cmd: Command to be performed on th rremote client.
54 :param verbosity: Verbosity level.
55 :type ssh_client: paramiko.SSHClient
60 _, stdout, stderr = ssh_client.exec_command(cmd)
61 c_stdout = stdout.read()
62 c_stderr = stderr.read()
64 print("DEBUG: Command output was:\n{0}".format(c_stdout))
65 print("DEBUG: Command stderr was:\n{0}".format(c_stderr))
69 # FIXME: Right now, this is really coded like a shell script, as one big
70 # function executed in sequence. This should be broken down into multiple
75 # Get our default interface IP address. This will become the default
76 # value for the "NFS Server IP" option.
78 gws = netifaces.gateways()
79 addrs = netifaces.ifaddresses(gws['default'][netifaces.AF_INET][1])
80 default_addr = addrs[netifaces.AF_INET][0]['addr']
83 # Verify CLI parameters and try to download our VPP image into a temporary
86 parser = argparse.ArgumentParser()
87 parser.add_argument("topology", help="the base topology to be started")
88 parser.add_argument("-k", "--keep", help="Keep (do not delete) the " +
89 "simulation in case of error", action='store_true')
90 parser.add_argument("-v", "--verbosity", action="count", default=0)
91 parser.add_argument("-nip", "--nfs-server-ip", help="NFS server (our) IP " +
92 "default is derived from routing table: " +
93 "{}".format(default_addr), default=default_addr)
94 parser.add_argument("-ns", "--nfs-scratch-directory",
95 help="Server location for NFS scratch diretory",
96 default="/nfs/scratch")
97 parser.add_argument("-nc", "--nfs-common-directory",
98 help="Server location for NFS common (read-only) " +
99 "directory", default="/nfs/common")
100 parser.add_argument("-wc", "--wait-count",
101 help="number of intervals to wait for simulation to " +
102 "be ready", type=int, default=24)
103 parser.add_argument("-wt", "--wait-time",
104 help="length of a single interval to wait for " +
105 "simulation to be ready", type=int, default=5)
106 parser.add_argument("-vip", "--virl-ip",
107 help="VIRL IP and Port (e.g. 127.0.0.1:19399)",
108 default="127.0.0.1:19399")
109 parser.add_argument("-u", "--username", help="VIRL username",
111 parser.add_argument("-p", "--password", help="VIRL password",
113 parser.add_argument("-su", "--ssh-user", help="SSH username",
115 parser.add_argument("-spr", "--ssh-privkey", help="SSH private keyfile",
116 default="/home/jenkins-in/.ssh/id_rsa_virl")
117 parser.add_argument("-spu", "--ssh-pubkey", help="SSH public keyfile",
118 default="/home/jenkins-in/.ssh/id_rsa_virl.pub")
119 parser.add_argument("-r", "--release", help="VM disk image/release " +
120 "(ex. \"csit-ubuntu-14.04.4_2016-05-25_1.0\")",
121 default="csit-ubuntu-14.04.4_2016-05-25_1.0")
122 parser.add_argument("--topology-directory", help="Topology directory",
123 default="/home/jenkins-in/testcase-infra/topologies")
125 args = parser.parse_args()
128 # Check if topology and template exist
130 if args.verbosity >= 2:
131 print "DEBUG: Running with topology {}".format(args.topology)
133 topology_virl_filename = os.path.join(args.topology_directory,
134 args.topology + ".virl")
135 topology_yaml_filename = os.path.join(args.topology_directory,
136 args.topology + ".yaml")
138 if not os.path.isfile(topology_virl_filename):
139 print "ERROR: Topology VIRL file {} does not exist".\
140 format(topology_virl_filename)
142 if not os.path.isfile(topology_yaml_filename):
143 print "ERROR: Topology YAML file {} does not exist".\
144 format(topology_yaml_filename)
148 # Start VIRL topology
150 if args.verbosity >= 1:
151 print "DEBUG: Starting VIRL topology"
152 temp_handle, temp_topology = tempfile.mkstemp()
153 with open(args.ssh_pubkey, 'r') as pubkey_file:
154 pub_key = pubkey_file.read().replace('\n', '')
155 with open(temp_topology, 'w') as new_file, \
156 open(topology_virl_filename, 'r') as old_file:
157 for line in old_file:
158 line = line.replace(" - VIRL-USER-SSH-PUBLIC-KEY", " - "+pub_key)
159 line = line.replace("$$NFS_SERVER_SCRATCH$$", \
160 args.nfs_server_ip+":"+args.nfs_scratch_directory)
161 line = line.replace("$$NFS_SERVER_COMMON$$", \
162 args.nfs_server_ip+":"+args.nfs_common_directory)
163 line = line.replace("$$VM_IMAGE$$", "server-"+args.release)
165 os.close(temp_handle)
168 new_file = open(temp_topology, 'rb')
169 headers = {'Content-Type': 'text/xml'}
170 req = requests.post('http://' + args.virl_ip + '/simengine/rest/launch',
172 auth=(args.username, args.password), data=new_file)
173 if args.verbosity >= 2:
174 print "DEBUG: - Response Code {}".format(req.status_code)
178 print "ERROR: Launching VIRL simulation - received invalid response"
180 os.remove(temp_topology)
183 if req.status_code != 200:
184 print "ERROR: Launching VIRL simulation - received status other " + \
186 print "Status was: {} \n".format(req.status_code)
187 print "Response content was: "
189 os.remove(temp_topology)
192 # If we got here, we had a good response. The response content is the
194 session_id = req.content
197 # Create simulation scratch directory. Move topology file into that
198 # directory. Copy or move debian packages into that directory.
200 scratch_directory = os.path.join(args.nfs_scratch_directory, session_id)
201 os.mkdir(scratch_directory)
202 shutil.move(temp_topology, os.path.join(scratch_directory,
203 "virl_topology.virl"))
204 os.mkdir(os.path.join(scratch_directory, "vpp"))
207 # Wait for simulation to become active
209 if args.verbosity >= 1:
210 print "DEBUG: Waiting for simulation to become active"
212 sim_is_started = False
215 count = args.wait_count
216 while (count > 0) and not sim_is_started:
217 time.sleep(args.wait_time)
220 req = requests.get('http://' + args.virl_ip + '/simengine/rest/nodes/' +
221 session_id, auth=(args.username, args.password))
227 # Flush the node list every time, keep the last one
230 # Hosts are the keys of the inner dictionary
231 for key in data[session_id].keys():
232 if data[session_id][key]['management-proxy'] == "self":
236 if data[session_id][key]['state'] == "ACTIVE":
238 if args.verbosity >= 2:
239 print "DEBUG: - Attempt {} out of {}, total {} hosts, {} active".\
240 format(args.wait_count-count, args.wait_count, total, active)
242 sim_is_started = True
244 if not sim_is_started:
245 print "ERROR: Simulation started OK but devices never changed to " + \
247 print "Last VIRL response:"
250 shutil.rmtree(scratch_directory)
251 req = requests.get('http://' + args.virl_ip +
252 '/simengine/rest/stop/' + session_id,
253 auth=(args.username, args.password))
255 if args.verbosity >= 2:
256 print "DEBUG: Nodes: " + ", ".join(nodelist)
259 # Fetch simulation's IPs and create files
260 # (ansible hosts file, topology YAML file)
262 req = requests.get('http://' + args.virl_ip +
263 '/simengine/rest/interfaces/' + session_id +
264 '?fetch-state=1', auth=(args.username, args.password))
267 # Populate node addresses
271 nodetype = re.split('[0-9]', key)[0]
272 if not nodetype in nodeaddrs:
273 nodeaddrs[nodetype] = {}
274 nodeaddrs[nodetype][key] = re.split('\\/', \
275 data[session_id][key]['management']['ip-address'])[0]
276 if args.verbosity >= 2:
277 print "DEBUG: Node {} is of type {} and has management IP {}".\
278 format(key, nodetype, nodeaddrs[nodetype][key])
281 for key2 in data[session_id][key]:
282 topology[key]["nic-"+key2] = data[session_id][key][key2]
283 if 'ip-address' in topology[key]["nic-"+key2]:
284 if topology[key]["nic-"+key2]['ip-address'] is not None:
285 topology[key]["nic-"+key2]['ip-addr'] = re.split('\\/', \
286 topology[key]["nic-"+key2]['ip-address'])[0]
289 ansiblehosts = open(os.path.join(scratch_directory, 'ansible-hosts'), 'w')
290 for key1 in nodeaddrs:
291 ansiblehosts.write("[{}]\n".format(key1))
292 for key2 in nodeaddrs[key1]:
293 ansiblehosts.write("{} hostname={}\n".format(nodeaddrs[key1][key2],
297 # Process topology YAML template
298 with open(args.ssh_privkey, 'r') as privkey_file:
299 priv_key = indent(privkey_file.read(), 6)
301 with open(os.path.join(scratch_directory, "topology.yaml"), 'w') as \
302 new_file, open(topology_yaml_filename, 'r') as old_file:
303 for line in old_file:
304 new_file.write(line.format(priv_key=priv_key, topology=topology))
307 # Wait for hosts to become reachable over SSH
309 if args.verbosity >= 1:
310 print "DEBUG: Waiting for hosts to become reachable using SSH"
313 count = args.wait_count
314 while (count > 0) and missing != 0:
315 time.sleep(args.wait_time)
320 if not os.path.exists(os.path.join(scratch_directory, key)):
322 if args.verbosity >= 2:
323 print "DEBUG: - Attempt {} out of {}, waiting for {} hosts".\
324 format(args.wait_count-count, args.wait_count, missing)
327 print "ERROR: Simulation started OK but {} hosts ".format(missing) + \
328 "never mounted their NFS directory"
330 shutil.rmtree(scratch_directory)
331 req = requests.get('http://' + args.virl_ip +
332 '/simengine/rest/stop/' + session_id,
333 auth=(args.username, args.password))
338 if args.verbosity >= 1:
339 print("DEBUG: Uprading VPP")
341 for key1 in nodeaddrs:
343 for key2 in nodeaddrs[key1]:
344 ipaddr = nodeaddrs[key1][key2]
345 if args.verbosity >= 2:
346 print("DEBUG: Upgrading VPP on node {}".format(ipaddr))
347 paramiko.util.log_to_file(os.path.join(scratch_directory,
349 client = paramiko.SSHClient()
350 client.load_system_host_keys()
351 client.load_host_keys("/dev/null")
352 client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
353 client.connect(ipaddr, username=args.ssh_user,
354 key_filename=args.ssh_privkey)
355 for cmd in inst_cmds:
356 perform_remote_command(client, cmd, args.verbosity)
359 # Write a file with timestamp to scratch directory. We can use this to track
360 # how long a simulation has been running.
362 with open(os.path.join(scratch_directory, 'start_time'), 'a') as \
364 timestampfile.write('{}\n'.format(int(time.time())))
369 if args.verbosity >= 1:
370 print "SESSION ID: {}".format(session_id)
372 print "{}".format(session_id)
374 if __name__ == "__main__":