Adding a new verification job for testapi UI.
[releng.git] / modules / opnfv / deployment / manager.py
1 ##############################################################################
2 # Copyright (c) 2017 Ericsson AB and others.
3 # Author: Jose Lausuch (jose.lausuch@ericsson.com)
4 # All rights reserved. This program and the accompanying materials
5 # are made available under the terms of the Apache License, Version 2.0
6 # which accompanies this distribution, and is available at
7 # http://www.apache.org/licenses/LICENSE-2.0
8 ##############################################################################
9
10 from abc import abstractmethod
11 import os
12
13
14 from opnfv.utils import opnfv_logger as logger
15 from opnfv.utils import ssh_utils
16
17 logger = logger.Logger(__name__).getLogger()
18
19
20 class Deployment(object):
21
22     def __init__(self,
23                  installer,
24                  installer_ip,
25                  scenario,
26                  pod,
27                  status,
28                  openstack_version,
29                  sdn_controller,
30                  nodes=None):
31
32         self.deployment_info = {
33             'installer': installer,
34             'installer_ip': installer_ip,
35             'scenario': scenario,
36             'pod': pod,
37             'status': status,
38             'openstack_version': openstack_version,
39             'sdn_controller': sdn_controller,
40             'nodes': nodes
41         }
42
43     def _get_openstack_release(self):
44         '''
45         Translates an openstack version into the release name
46         '''
47         os_versions = {
48             '12': 'Liberty',
49             '13': 'Mitaka',
50             '14': 'Newton',
51             '15': 'Ocata',
52             '16': 'Pike',
53             '17': 'Queens'
54         }
55         try:
56             version = self.deployment_info['openstack_version'].split('.')[0]
57             name = os_versions[version]
58             return name
59         except Exception:
60             return 'Unknown release'
61
62     def get_dict(self):
63         '''
64         Returns a dictionary will all the attributes
65         '''
66         return self.deployment_info
67
68     def __str__(self):
69         '''
70         Override of the str method
71         '''
72         s = '''
73         INSTALLER:    {installer}
74         SCENARIO:     {scenario}
75         INSTALLER IP: {installer_ip}
76         POD:          {pod}
77         STATUS:       {status}
78         OPENSTACK:    {openstack_version} ({openstack_release})
79         SDN:          {sdn_controller}
80         NODES:
81     '''.format(installer=self.deployment_info['installer'],
82                scenario=self.deployment_info['scenario'],
83                installer_ip=self.deployment_info['installer_ip'],
84                pod=self.deployment_info['pod'],
85                status=self.deployment_info['status'],
86                openstack_version=self.deployment_info[
87             'openstack_version'],
88             openstack_release=self._get_openstack_release(),
89             sdn_controller=self.deployment_info['sdn_controller'])
90
91         for node in self.deployment_info['nodes']:
92             s += '{node_object}\n'.format(node_object=node)
93
94         return s
95
96
97 class Role():
98     INSTALLER = 'installer'
99     CONTROLLER = 'controller'
100     COMPUTE = 'compute'
101     ODL = 'opendaylight'
102     ONOS = 'onos'
103
104
105 class NodeStatus():
106     STATUS_OK = 'active'
107     STATUS_INACTIVE = 'inactive'
108     STATUS_OFFLINE = 'offline'
109     STATUS_ERROR = 'error'
110     STATUS_UNUSED = 'unused'
111     STATUS_UNKNOWN = 'unknown'
112
113
114 class Node(object):
115
116     def __init__(self,
117                  id,
118                  ip,
119                  name,
120                  status,
121                  roles=None,
122                  ssh_client=None,
123                  info=None):
124         self.id = id
125         self.ip = ip
126         self.name = name
127         self.status = status
128         self.ssh_client = ssh_client
129         self.roles = roles
130         self.info = info
131
132         self.cpu_info = 'unknown'
133         self.memory = 'unknown'
134         self.ovs = 'unknown'
135
136         if ssh_client and Role.INSTALLER not in self.roles:
137             sys_info = self.get_system_info()
138             self.cpu_info = sys_info['cpu_info']
139             self.memory = sys_info['memory']
140             self.ovs = self.get_ovs_info()
141
142     def get_file(self, src, dest):
143         '''
144         SCP file from a node
145         '''
146         if self.status is not NodeStatus.STATUS_OK:
147             logger.info("The node %s is not active" % self.ip)
148             return 1
149         logger.info("Fetching %s from %s" % (src, self.ip))
150         get_file_result = ssh_utils.get_file(self.ssh_client, src, dest)
151         if get_file_result is None:
152             logger.error("SFTP failed to retrieve the file.")
153         else:
154             logger.info("Successfully copied %s:%s to %s" %
155                         (self.ip, src, dest))
156         return get_file_result
157
158     def put_file(self, src, dest):
159         '''
160         SCP file to a node
161         '''
162         if self.status is not NodeStatus.STATUS_OK:
163             logger.info("The node %s is not active" % self.ip)
164             return 1
165         logger.info("Copying %s to %s" % (src, self.ip))
166         put_file_result = ssh_utils.put_file(self.ssh_client, src, dest)
167         if put_file_result is None:
168             logger.error("SFTP failed to retrieve the file.")
169         else:
170             logger.info("Successfully copied %s to %s:%s" %
171                         (src, dest, self.ip))
172         return put_file_result
173
174     def run_cmd(self, cmd):
175         '''
176         Run command remotely on a node
177         '''
178         if self.status is not NodeStatus.STATUS_OK:
179             logger.error(
180                 "Error running command %s. The node %s is not active"
181                 % (cmd, self.ip))
182             return None
183         _, stdout, stderr = (self.ssh_client.exec_command(cmd))
184         error = stderr.readlines()
185         if len(error) > 0:
186             logger.error("error %s" % ''.join(error))
187             return None
188         output = ''.join(stdout.readlines()).rstrip()
189         return output
190
191     def get_dict(self):
192         '''
193         Returns a dictionary with all the attributes
194         '''
195         return {
196             'id': self.id,
197             'ip': self.ip,
198             'name': self.name,
199             'status': self.status,
200             'roles': self.roles,
201             'cpu_info': self.cpu_info,
202             'memory': self.memory,
203             'ovs': self.ovs,
204             'info': self.info
205         }
206
207     def is_active(self):
208         '''
209         Returns if the node is active
210         '''
211         if self.status == NodeStatus.STATUS_OK:
212             return True
213         return False
214
215     def is_controller(self):
216         '''
217         Returns if the node is a controller
218         '''
219         return Role.CONTROLLER in self.roles
220
221     def is_compute(self):
222         '''
223         Returns if the node is a compute
224         '''
225         return Role.COMPUTE in self.roles
226
227     def is_odl(self):
228         '''
229         Returns if the node is an opendaylight
230         '''
231         return Role.ODL in self.roles
232
233     def is_onos(self):
234         '''
235         Returns if the node is an ONOS
236         '''
237         return Role.ONOS in self.roles
238
239     def get_ovs_info(self):
240         '''
241         Returns the ovs version installed
242         '''
243         if self.is_active():
244             cmd = "ovs-vsctl --version|head -1| sed 's/^.*) //'"
245             return self.run_cmd(cmd)
246         return None
247
248     def get_system_info(self):
249         '''
250         Returns the ovs version installed
251         '''
252         cmd = 'grep MemTotal /proc/meminfo'
253         memory = self.run_cmd(cmd).partition('MemTotal:')[-1].strip().encode()
254
255         cpu_info = {}
256         cmd = 'lscpu'
257         result = self.run_cmd(cmd)
258         for line in result.splitlines():
259             if line.startswith('CPU(s)'):
260                 cpu_info['num_cpus'] = line.split(' ')[-1].encode()
261             elif line.startswith('Thread(s) per core'):
262                 cpu_info['threads/core'] = line.split(' ')[-1].encode()
263             elif line.startswith('Core(s) per socket'):
264                 cpu_info['cores/socket'] = line.split(' ')[-1].encode()
265             elif line.startswith('Model name'):
266                 cpu_info['model'] = line.partition(
267                     'Model name:')[-1].strip().encode()
268             elif line.startswith('Architecture'):
269                 cpu_info['arch'] = line.split(' ')[-1].encode()
270
271         return {'memory': memory, 'cpu_info': cpu_info}
272
273     def __str__(self):
274         return '''
275             name:    {name}
276             id:      {id}
277             ip:      {ip}
278             status:  {status}
279             roles:   {roles}
280             cpu:     {cpu_info}
281             memory:  {memory}
282             ovs:     {ovs}
283             info:    {info}'''.format(name=self.name,
284                                       id=self.id,
285                                       ip=self.ip,
286                                       status=self.status,
287                                       roles=self.roles,
288                                       cpu_info=self.cpu_info,
289                                       memory=self.memory,
290                                       ovs=self.ovs,
291                                       info=self.info)
292
293
294 class DeploymentHandler(object):
295
296     EX_OK = os.EX_OK
297     EX_ERROR = os.EX_SOFTWARE
298     FUNCTION_NOT_IMPLEMENTED = "Function not implemented by adapter!"
299
300     def __init__(self,
301                  installer,
302                  installer_ip,
303                  installer_user,
304                  installer_pwd=None,
305                  pkey_file=None):
306
307         self.installer = installer.lower()
308         self.installer_ip = installer_ip
309         self.installer_user = installer_user
310         self.installer_pwd = installer_pwd
311         self.pkey_file = pkey_file
312
313         if pkey_file is not None and not os.path.isfile(pkey_file):
314             raise Exception(
315                 'The private key file %s does not exist!' % pkey_file)
316
317         self.installer_connection = ssh_utils.get_ssh_client(
318             hostname=self.installer_ip,
319             username=self.installer_user,
320             password=self.installer_pwd,
321             pkey_file=self.pkey_file)
322
323         if self.installer_connection:
324             self.installer_node = Node(id='',
325                                        ip=installer_ip,
326                                        name=installer,
327                                        status=NodeStatus.STATUS_OK,
328                                        ssh_client=self.installer_connection,
329                                        roles=Role.INSTALLER)
330         else:
331             raise Exception(
332                 'Cannot establish connection to the installer node!')
333
334         self.nodes = self.get_nodes()
335
336     @abstractmethod
337     def get_openstack_version(self):
338         '''
339         Returns a string of the openstack version (nova-compute)
340         '''
341         raise Exception(DeploymentHandler.FUNCTION_NOT_IMPLEMENTED)
342
343     @abstractmethod
344     def get_sdn_version(self):
345         '''
346         Returns a string of the sdn controller and its version, if exists
347         '''
348         raise Exception(DeploymentHandler.FUNCTION_NOT_IMPLEMENTED)
349
350     @abstractmethod
351     def get_deployment_status(self):
352         '''
353         Returns a string of the status of the deployment
354         '''
355         raise Exception(DeploymentHandler.FUNCTION_NOT_IMPLEMENTED)
356
357     @abstractmethod
358     def get_nodes(self, options=None):
359         '''
360             Generates a list of all the nodes in the deployment
361         '''
362         raise Exception(DeploymentHandler.FUNCTION_NOT_IMPLEMENTED)
363
364     def get_installer_node(self):
365         '''
366             Returns the installer node object
367         '''
368         return self.installer_node
369
370     def get_arch(self):
371         '''
372             Returns the architecture of the first compute node found
373         '''
374         arch = None
375         for node in self.nodes:
376             if node.is_compute():
377                 arch = node.cpu_info.get('arch', None)
378                 if arch:
379                     break
380         return arch
381
382     def get_deployment_info(self):
383         '''
384             Returns an object of type Deployment
385         '''
386         return Deployment(installer=self.installer,
387                           installer_ip=self.installer_ip,
388                           scenario=os.getenv('DEPLOY_SCENARIO', 'Unknown'),
389                           status=self.get_deployment_status(),
390                           pod=os.getenv('NODE_NAME', 'Unknown'),
391                           openstack_version=self.get_openstack_version(),
392                           sdn_controller=self.get_sdn_version(),
393                           nodes=self.nodes)