Publish singlevm scenarios
[functest.git] / functest / core / singlevm.py
1 #!/usr/bin/env python
2
3 # Copyright (c) 2018 Orange and others.
4 #
5 # All rights reserved. This program and the accompanying materials
6 # are made available under the terms of the Apache License, Version 2.0
7 # which accompanies this distribution, and is available at
8 # http://www.apache.org/licenses/LICENSE-2.0
9
10 """Ease deploying a single VM reachable via ssh
11
12 It offers a simple way to create all tenant network ressources + a VM for
13 advanced testcases (e.g. deploying an orchestrator).
14 """
15
16 import logging
17 import os
18 import tempfile
19 import time
20 import uuid
21
22 import os_client_config
23 import paramiko
24 import shade
25 from xtesting.core import testcase
26
27 from functest.core import tenantnetwork
28 from functest.utils import config
29
30
31 class SingleVm1(tenantnetwork.TenantNetwork1):
32     """Deploy a single VM reachable via ssh (scenario1)
33
34     It inherits from TenantNetwork1 which creates all network resources and
35     completes it by booting a VM attached to that network.
36
37     It ensures that all testcases inheriting from SingleVm1 could work
38     without specific configurations (or at least read the same config data).
39     """
40     # pylint: disable=too-many-instance-attributes
41
42     __logger = logging.getLogger(__name__)
43     filename = '/home/opnfv/functest/images/cirros-0.4.0-x86_64-disk.img'
44     flavor_ram = 1024
45     flavor_vcpus = 1
46     flavor_disk = 1
47     username = 'cirros'
48     ssh_connect_timeout = 60
49
50     def __init__(self, **kwargs):
51         if "case_name" not in kwargs:
52             kwargs["case_name"] = 'singlevm1'
53         super(SingleVm1, self).__init__(**kwargs)
54         self.orig_cloud = self.cloud
55         self.image = None
56         self.sshvm = None
57         self.flavor = None
58         self.sec = None
59         self.fip = None
60         self.keypair = None
61         self.ssh = paramiko.SSHClient()
62         (_, self.key_filename) = tempfile.mkstemp()
63
64     def _publish_image(self):
65         assert self.cloud
66         meta = getattr(
67             config.CONF, '{}_extra_properties'.format(self.case_name), None)
68         self.image = self.cloud.create_image(
69             '{}-img_{}'.format(self.case_name, self.guid),
70             filename=getattr(
71                 config.CONF, '{}_image'.format(self.case_name),
72                 self.filename),
73             meta=meta)
74         self.__logger.debug("image: %s", self.image)
75
76     def create_sg_rules(self):
77         """Create the security group
78
79         It can be overriden to set other rules according to the services
80         running in the VM
81
82         Raises: Exception on error
83         """
84         assert self.cloud
85         self.sec = self.cloud.create_security_group(
86             '{}-sg_{}'.format(self.case_name, self.guid),
87             'created by OPNFV Functest ({})'.format(self.case_name))
88         self.cloud.create_security_group_rule(
89             self.sec.id, port_range_min='22', port_range_max='22',
90             protocol='tcp', direction='ingress')
91         self.cloud.create_security_group_rule(
92             self.sec.id, protocol='icmp', direction='ingress')
93
94     def _boot_vm(self):
95         assert self.orig_cloud
96         assert self.cloud
97         self.flavor = self.orig_cloud.create_flavor(
98             '{}-flavor_{}'.format(self.case_name, self.guid),
99             getattr(config.CONF, '{}_flavor_ram'.format(self.case_name),
100                     self.flavor_ram),
101             getattr(config.CONF, '{}_flavor_vcpus'.format(self.case_name),
102                     self.flavor_vcpus),
103             getattr(config.CONF, '{}_flavor_disk'.format(self.case_name),
104                     self.flavor_disk))
105         self.__logger.debug("flavor: %s", self.flavor)
106         self.cloud.set_flavor_specs(
107             self.flavor.id, getattr(config.CONF, 'flavor_extra_specs', {}))
108
109         self.keypair = self.cloud.create_keypair(
110             '{}-kp_{}'.format(self.case_name, self.guid))
111         self.__logger.debug("keypair: %s", self.keypair)
112         self.__logger.debug("private_key: %s", self.keypair.private_key)
113         with open(self.key_filename, 'w') as private_key_file:
114             private_key_file.write(self.keypair.private_key)
115
116         self.sshvm = self.cloud.create_server(
117             '{}-vm_{}'.format(self.case_name, self.guid),
118             image=self.image.id, flavor=self.flavor.id,
119             key_name=self.keypair.id,
120             auto_ip=False, wait=True,
121             network=self.network.id,
122             security_groups=[self.sec.id])
123         self.__logger.debug("vm: %s", self.sshvm)
124         self.fip = self.cloud.create_floating_ip(
125             network=self.ext_net.id, server=self.sshvm)
126         self.__logger.debug("floating_ip: %s", self.fip)
127         self.sshvm = self.cloud.wait_for_server(self.sshvm, auto_ip=False)
128
129     def _connect(self):
130         assert self.cloud
131         p_console = self.cloud.get_server_console(self.sshvm.id)
132         self.__logger.debug("vm console: \n%s", p_console)
133         self.ssh.set_missing_host_key_policy(paramiko.client.AutoAddPolicy())
134         for loop in range(6):
135             try:
136                 self.ssh.connect(
137                     self.sshvm.public_v4,
138                     username=getattr(
139                         config.CONF,
140                         '{}_image_user'.format(self.case_name), self.username),
141                     key_filename=self.key_filename,
142                     timeout=getattr(
143                         config.CONF,
144                         '{}_vm_ssh_connect_timeout'.format(self.case_name),
145                         self.ssh_connect_timeout))
146                 break
147             except Exception:  # pylint: disable=broad-except
148                 self.__logger.info(
149                     "try %s: cannot connect to %s", loop + 1,
150                     self.sshvm.public_v4)
151                 time.sleep(10)
152         else:
153             self.__logger.error("cannot connect to %s", self.sshvm.public_v4)
154             return False
155         return True
156
157     def execute(self):
158         """Say hello world via ssh
159
160         It can be overriden to execute any command.
161
162         Returns: echo exit codes
163         """
164         (_, stdout, _) = self.ssh.exec_command('echo Hello World')
165         self.__logger.info("output:\n%s", stdout.read())
166         return stdout.channel.recv_exit_status()
167
168     def run(self, **kwargs):
169         """Boot the new VM
170
171         Here are the main actions:
172         - publish the image
173         - add a new ssh key
174         - boot the VM
175         - create the security group
176         - execute the right command over ssh
177
178         Returns:
179         - TestCase.EX_OK
180         - TestCase.EX_RUN_ERROR on error
181         """
182         status = testcase.TestCase.EX_RUN_ERROR
183         try:
184             assert self.cloud
185             super(SingleVm1, self).run(**kwargs)
186             self.result = 0
187             self._publish_image()
188             self.create_sg_rules()
189             self._boot_vm()
190             assert self._connect()
191             if not self.execute():
192                 self.result = 100
193                 status = testcase.TestCase.EX_OK
194         except Exception:  # pylint: disable=broad-except
195             self.__logger.exception('Cannot run %s', self.case_name)
196         finally:
197             self.stop_time = time.time()
198         return status
199
200     def clean(self):
201         try:
202             assert self.orig_cloud
203             assert self.cloud
204             self.cloud.delete_server(self.sshvm, wait=True)
205             self.cloud.delete_security_group(self.sec.id)
206             self.cloud.delete_image(self.image)
207             self.orig_cloud.delete_flavor(self.flavor.id)
208             self.cloud.delete_keypair(self.keypair.id)
209             self.cloud.delete_floating_ip(self.fip.id)
210             self.cloud.delete_image(self.image)
211         except Exception:  # pylint: disable=broad-except
212             pass
213
214
215 class SingleVm2(SingleVm1):
216     """Deploy a single VM reachable via ssh (scenario2)
217
218     It creates new user/project before creating and configuring all tenant
219     network ressources and vms required by advanced testcases.
220
221     It ensures that all testcases inheriting from SingleVm2 could work
222     without specific configurations (or at least read the same config data).
223     """
224
225     __logger = logging.getLogger(__name__)
226
227     def __init__(self, **kwargs):
228         if "case_name" not in kwargs:
229             kwargs["case_name"] = 'singlevm2'
230         super(SingleVm2, self).__init__(**kwargs)
231         try:
232             assert self.cloud
233             self.domain = self.cloud.get_domain(
234                 name_or_id=self.cloud.auth.get(
235                     "project_domain_name", "Default"))
236         except Exception:  # pylint: disable=broad-except
237             self.domain = None
238             self.__logger.exception("Cannot connect to Cloud")
239         self.project = None
240         self.user = None
241         self.orig_cloud = None
242         self.password = str(uuid.uuid4())
243
244     def run(self, **kwargs):
245         assert self.cloud
246         assert self.domain
247         try:
248             self.project = self.cloud.create_project(
249                 name='{}-project_{}'.format(self.case_name, self.guid),
250                 description="Created by OPNFV Functest: {}".format(
251                     self.case_name),
252                 domain_id=self.domain.id)
253             self.__logger.debug("project: %s", self.project)
254             self.user = self.cloud.create_user(
255                 name='{}-user_{}'.format(self.case_name, self.guid),
256                 password=self.password,
257                 default_project=self.project.id,
258                 domain_id=self.domain.id)
259             self.__logger.debug("user: %s", self.user)
260             self.orig_cloud = self.cloud
261             os.environ["OS_USERNAME"] = self.user.name
262             os.environ["OS_PROJECT_NAME"] = self.user.default_project_id
263             cloud_config = os_client_config.get_config()
264             self.cloud = shade.OpenStackCloud(cloud_config=cloud_config)
265             os.environ["OS_USERNAME"] = self.orig_cloud.auth["username"]
266             os.environ["OS_PROJECT_NAME"] = self.orig_cloud.auth[
267                 "project_name"]
268         except Exception:  # pylint: disable=broad-except
269             self.__logger.exception("Cannot create user or project")
270             return testcase.TestCase.EX_RUN_ERROR
271         return super(SingleVm2, self).run(**kwargs)
272
273     def clean(self):
274         try:
275             assert self.cloud
276             assert self.orig_cloud
277             super(SingleVm2, self).clean()
278             assert self.user.id
279             assert self.project.id
280             self.orig_cloud.delete_user(self.user.id)
281             self.orig_cloud.delete_project(self.project.id)
282         except Exception:  # pylint: disable=broad-except
283             self.__logger.exception("cannot clean all ressources")