Browse Source

Merge "Move functional test to murano-tempest-plugin"

Zuul 8 months ago
parent
commit
7a908fff2c
17 changed files with 1095 additions and 0 deletions
  1. 0
    0
      murano_tempest_tests/tests/functional/__init__.py
  2. 0
    0
      murano_tempest_tests/tests/functional/common/__init__.py
  3. 49
    0
      murano_tempest_tests/tests/functional/common/tempest_utils.py
  4. 550
    0
      murano_tempest_tests/tests/functional/common/utils.py
  5. 30
    0
      murano_tempest_tests/tests/functional/common/zip_utils_mixin.py
  6. 0
    0
      murano_tempest_tests/tests/functional/integration/__init__.py
  7. 157
    0
      murano_tempest_tests/tests/functional/integration/integration_base.py
  8. 32
    0
      murano_tempest_tests/tests/functional/integration/io.murano.apps.test.MistralShowcaseApp/Classes/MistralShowcaseApp.yaml
  9. 24
    0
      murano_tempest_tests/tests/functional/integration/io.murano.apps.test.MistralShowcaseApp/Resources/TestEcho_MistralWorkflow.yaml
  10. 10
    0
      murano_tempest_tests/tests/functional/integration/io.murano.apps.test.MistralShowcaseApp/manifest.yaml
  11. 48
    0
      murano_tempest_tests/tests/functional/integration/io.murano.apps.test.PolicyEnforcementTestApp/Classes/PolicyEnforcementTestApp.yaml
  12. 10
    0
      murano_tempest_tests/tests/functional/integration/io.murano.apps.test.PolicyEnforcementTestApp/manifest.yaml
  13. 18
    0
      murano_tempest_tests/tests/functional/integration/rules_murano_action.txt
  14. 7
    0
      murano_tempest_tests/tests/functional/integration/rules_murano_system.txt
  15. 62
    0
      murano_tempest_tests/tests/functional/integration/test_mistral.py
  16. 89
    0
      murano_tempest_tests/tests/functional/integration/test_policy_enf.py
  17. 9
    0
      requirements.txt

+ 0
- 0
murano_tempest_tests/tests/functional/__init__.py View File


+ 0
- 0
murano_tempest_tests/tests/functional/common/__init__.py View File


+ 49
- 0
murano_tempest_tests/tests/functional/common/tempest_utils.py View File

@@ -0,0 +1,49 @@
1
+# Copyright (c) 2015 OpenStack Foundation
2
+#
3
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
4
+# not use this file except in compliance with the License. You may obtain
5
+# a copy of the License at
6
+#
7
+# http://www.apache.org/licenses/LICENSE-2.0
8
+#
9
+# Unless required by applicable law or agreed to in writing, software
10
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12
+# License for the specific language governing permissions and limitations
13
+# under the License.
14
+
15
+import congressclient.v1.client as cclient
16
+from keystoneauth1 import identity
17
+from keystoneauth1 import session as ksasession
18
+import keystoneclient.v3 as ksclient
19
+from tempest import config
20
+
21
+import murano_tempest_tests.tests.functional.common.utils as common_utils
22
+
23
+CONF = config.CONF
24
+
25
+
26
+class TempestDeployTestMixin(common_utils.DeployTestMixin):
27
+    """Overrides methods to use tempest configuration."""
28
+
29
+    @staticmethod
30
+    @common_utils.memoize
31
+    def keystone_client():
32
+        return ksclient.Client(username=CONF.auth.admin_username,
33
+                               password=CONF.auth.admin_password,
34
+                               tenant_name=CONF.auth.admin_project_name,
35
+                               auth_url=CONF.identity.uri_v3)
36
+
37
+    @staticmethod
38
+    @common_utils.memoize
39
+    def congress_client():
40
+        auth = identity.v3.Password(
41
+            auth_url=CONF.identity.uri_v3,
42
+            username=CONF.auth.admin_username,
43
+            password=CONF.auth.admin_password,
44
+            project_name=CONF.auth.admin_project_name,
45
+            user_domain_name=CONF.auth.admin_domain_name,
46
+            project_domain_name=CONF.auth.admin_domain_name)
47
+        session = ksasession.Session(auth=auth)
48
+        return cclient.Client(session=session,
49
+                              service_type='policy')

+ 550
- 0
murano_tempest_tests/tests/functional/common/utils.py View File

@@ -0,0 +1,550 @@
1
+# Copyright (c) 2015 OpenStack Foundation
2
+#
3
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
4
+# not use this file except in compliance with the License. You may obtain
5
+# a copy of the License at
6
+#
7
+# http://www.apache.org/licenses/LICENSE-2.0
8
+#
9
+# Unless required by applicable law or agreed to in writing, software
10
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12
+# License for the specific language governing permissions and limitations
13
+# under the License.
14
+
15
+import collections
16
+import contextlib
17
+import json
18
+import os
19
+import random
20
+import socket
21
+import telnetlib
22
+import time
23
+
24
+from heatclient import client as heatclient
25
+from keystoneclient import exceptions as ks_exceptions
26
+import keystoneclient.v3 as ksclient
27
+from muranoclient import client as mclient
28
+import muranoclient.common.exceptions as exceptions
29
+from muranoclient.glance import client as glare_client
30
+from oslo_log import log as logging
31
+from tempest import config
32
+import yaml
33
+
34
+import murano_tempest_tests.tests.functional.common.zip_utils_mixin \
35
+    as zip_utils
36
+
37
+
38
+CONF = config.CONF
39
+
40
+LOG = logging.getLogger(__name__)
41
+
42
+
43
+SessionState = collections.namedtuple('SessionState', [
44
+    'OPENED', 'DEPLOYING', 'DEPLOYED', 'DEPLOY_FAILURE', 'DELETING',
45
+    'DELETE_FAILURE'
46
+])(
47
+    OPENED='opened',
48
+    DEPLOYING='deploying',
49
+    DEPLOYED='deployed',
50
+    DEPLOY_FAILURE='deploy failure',
51
+    DELETING='deleting',
52
+    DELETE_FAILURE='delete failure'
53
+)
54
+
55
+
56
+@contextlib.contextmanager
57
+def ignored(*exceptions):
58
+    try:
59
+        yield
60
+    except exceptions:
61
+        pass
62
+
63
+
64
+def memoize(f):
65
+    """Saves result of decorated function to cache
66
+
67
+    Decorator, which saves result of a decorated function
68
+    to cache.
69
+    TTL for cache is 1800 sec
70
+
71
+    :param f: decorated function
72
+    :return: saved result of a decorated function
73
+    """
74
+    cache = {}
75
+
76
+    def decorated_function(*args):
77
+        if args in cache:
78
+            if time.time() - cache[args][1] < 1800:
79
+                return cache[args][0]
80
+            else:
81
+                cache[args] = (f(*args), time.time())
82
+                return cache[args][0]
83
+        else:
84
+            cache[args] = (f(*args), time.time())
85
+            return cache[args][0]
86
+
87
+    return decorated_function
88
+
89
+
90
+class DeployTestMixin(zip_utils.ZipUtilsMixin):
91
+
92
+    @staticmethod
93
+    @memoize
94
+    def keystone_client():
95
+        return ksclient.Client(username=CONF.auth.admin_username,
96
+                               password=CONF.auth.admin_password,
97
+                               tenant_name=CONF.auth.admin_project_name,
98
+                               auth_url=CONF.identity.uri_v3)
99
+
100
+    @classmethod
101
+    @memoize
102
+    def heat_client(cls):
103
+        heat_url = cls.keystone_client().service_catalog.url_for(
104
+            service_type='orchestration', endpoint_type='publicURL')
105
+        return heatclient.Client('1',
106
+                                 endpoint=heat_url,
107
+                                 token=cls.keystone_client().auth_token)
108
+
109
+    @classmethod
110
+    @memoize
111
+    def murano_client(cls):
112
+        murano_url = cls.get_murano_url()
113
+        if CONF.application_catalog.glare_backend:
114
+            glare_endpoint = "http://127.0.0.1:9494"
115
+            artifacts_client = glare_client.Client(
116
+                endpoint=glare_endpoint,
117
+                token=cls.keystone_client().auth_token,
118
+                insecure=False, key_file=None, ca_file=None, cert_file=None,
119
+                type_name="murano", type_version=1)
120
+        else:
121
+            artifacts_client = None
122
+        return mclient.Client('1',
123
+                              artifacts_client=artifacts_client,
124
+                              endpoint=murano_url,
125
+                              token=cls.keystone_client().auth_token)
126
+
127
+# --------------------------Specific test methods------------------------------
128
+
129
+    @classmethod
130
+    def deploy_apps(cls, name, *apps):
131
+        """Create and deploy environment.
132
+
133
+        :param name: Murano environment name
134
+        :param apps: App(s), described in JSON format
135
+        :return: Murano environment
136
+        """
137
+        environment = cls.murano_client().environments.create({'name': name})
138
+        cls.init_list("_environments")
139
+        cls._environments.append(environment)
140
+        session = cls.murano_client().sessions.configure(environment.id)
141
+        for app in apps:
142
+            cls.murano_client().services.post(
143
+                environment.id,
144
+                path='/',
145
+                data=app,
146
+                session_id=session.id)
147
+        cls.murano_client().sessions.deploy(environment.id, session.id)
148
+        return environment
149
+
150
+    @classmethod
151
+    def wait_for_final_status(cls, environment, timeout=300):
152
+        """Function for wait final status of environment.
153
+
154
+        :param environment: Murano environment.
155
+        :param timeout: Timeout for waiting environment to get any status
156
+               excluding DEPLOYING state
157
+        """
158
+        start_time = time.time()
159
+        status = environment.manager.get(environment.id).status
160
+        while SessionState.DEPLOYING == status:
161
+            if time.time() - start_time > timeout:
162
+                err_msg = ('Deployment not finished in {amount} seconds'
163
+                           .format(amount=timeout))
164
+                LOG.error(err_msg)
165
+                raise RuntimeError(err_msg)
166
+            time.sleep(5)
167
+            status = environment.manager.get(environment.id).status
168
+        dep = cls.murano_client().deployments.list(environment.id)
169
+        reports = cls.murano_client().deployments.reports(environment.id,
170
+                                                          dep[0].id)
171
+        return status, ", ".join([r.text for r in reports])
172
+
173
+# -----------------------------Reports methods---------------------------------
174
+
175
+    @classmethod
176
+    def get_last_deployment(cls, environment):
177
+        """Gets last deployment of Murano environment.
178
+
179
+        :param environment: Murano environment
180
+        :return:
181
+        """
182
+        deployments = cls.murano_client().deployments.list(environment.id)
183
+        return deployments[0]
184
+
185
+    @classmethod
186
+    def get_deployment_report(cls, environment, deployment):
187
+        """Gets reports for environment with specific deployment.
188
+
189
+        :param environment: Murano environment.
190
+        :param deployment: Murano deployment for certain environment
191
+        :return:
192
+        """
193
+        history = ''
194
+        report = cls.murano_client().deployments.reports(
195
+            environment.id, deployment.id)
196
+        for status in report:
197
+            history += '\t{0} - {1}\n'.format(status.created, status.text)
198
+        return history
199
+
200
+    @classmethod
201
+    def _log_report(cls, environment):
202
+        """Used for logging reports on failures.
203
+
204
+        :param environment: Murano environment.
205
+        """
206
+        deployment = cls.get_last_deployment(environment)
207
+        try:
208
+            details = deployment.result['result']['details']
209
+            LOG.warning('Details:\n {details}'.format(details=details))
210
+        except Exception as e:
211
+            LOG.error(e)
212
+        report = cls.get_deployment_report(environment, deployment)
213
+        LOG.debug('Report:\n {report}\n'.format(report=report))
214
+
215
+# -----------------------------Service methods---------------------------------
216
+
217
+    @classmethod
218
+    def add_service(cls, environment, data, session, to_dict=False):
219
+        """This function adds a specific service to environment.
220
+
221
+        :param environment: Murano environment
222
+        :param data: JSON with specific servive to add into
223
+        :param session: Session that is open for environment
224
+        :param to_dict: If True - returns a JSON object with service
225
+                        If False - returns a specific class <Service>
226
+        """
227
+
228
+        LOG.debug('Added service:\n {data}'.format(data=data))
229
+        service = cls.murano_client().services.post(environment.id,
230
+                                                    path='/', data=data,
231
+                                                    session_id=session.id)
232
+        if to_dict:
233
+            return cls._convert_service(service)
234
+        else:
235
+            return service
236
+
237
+    @classmethod
238
+    def services_list(cls, environment):
239
+        """Get a list of environment services.
240
+
241
+        :param environment: Murano environment
242
+        :return: List of <Service> objects
243
+        """
244
+        return cls.murano_client().services.list(environment.id)
245
+
246
+    @classmethod
247
+    def get_service(cls, environment, service_name, to_dict=True):
248
+        """Get a service with specific name from environment.
249
+
250
+        :param to_dict: Convert service to JSON or not to convert
251
+        :param environment: Murano environment
252
+        :param service_name: Service name
253
+        :return: JSON or <Service> object
254
+        """
255
+        for service in cls.services_list(environment):
256
+            if service.name == service_name:
257
+                return cls._convert_service(service) if to_dict else service
258
+
259
+    @classmethod
260
+    def _convert_service(cls, service):
261
+        """Converts a <Service> to JSON object.
262
+
263
+        :param service: <Service> object
264
+        :return: JSON object
265
+        """
266
+        component = service.to_dict()
267
+        component = json.dumps(component)
268
+        return yaml.safe_load(component)
269
+
270
+    @classmethod
271
+    def get_service_id(cls, service):
272
+        """Gets id on <Service> object.
273
+
274
+        :param service: <Service> object
275
+        :return: ID of the Service
276
+        """
277
+        serv = cls._convert_service(service)
278
+        serv_id = serv['?']['id']
279
+        return serv_id
280
+
281
+    @classmethod
282
+    def delete_service(cls, environment, session, service):
283
+        """This function removes a specific service from environment.
284
+
285
+        :param environment: Murano environment
286
+        :param session: Session fir urano environment
287
+        :param service: <Service> object
288
+        :return: Updated murano environment
289
+        """
290
+        cls.murano_client().services.delete(
291
+            environment.id, path='/{0}'.format(cls.get_service_id(service)),
292
+            session_id=session.id)
293
+        LOG.debug('Service with name {0} from environment {1} successfully '
294
+                  'removed'.format(environment.name, service.name))
295
+        updated_env = cls.get_environment(environment)
296
+        return updated_env
297
+
298
+
299
+# -----------------------------Packages methods--------------------------------
300
+
301
+    @classmethod
302
+    def upload_package(cls, package_name, body, app):
303
+        """Uploads a .zip package with parameters to Murano.
304
+
305
+        :param package_name: Package name in Murano repository
306
+        :param body: Categories, tags, etc.
307
+                     e.g. {
308
+                           "categories": ["Application Servers"],
309
+                           "tags": ["tag"]
310
+                           }
311
+        :param app: Correct .zip archive with the application
312
+        :return: Package
313
+        """
314
+        files = {'{0}'.format(package_name): open(app, 'rb')}
315
+        package = cls.murano_client().packages.create(body, files)
316
+        cls.init_list("_packages")
317
+        cls._packages.append(package)
318
+        return package
319
+
320
+# ------------------------------Common methods---------------------------------
321
+
322
+    @classmethod
323
+    def rand_name(cls, name='murano'):
324
+        """Generates random string.
325
+
326
+        :param name: Basic name
327
+        :return:
328
+        """
329
+        return name + str(random.randint(1, 0x7fffffff))
330
+
331
+    @classmethod
332
+    def init_list(cls, list_name):
333
+        if not hasattr(cls, list_name):
334
+            setattr(cls, list_name, [])
335
+
336
+    @classmethod
337
+    def get_murano_url(cls):
338
+        try:
339
+            url = cls.keystone_client().service_catalog.url_for(
340
+                service_type='application-catalog', endpoint_type='publicURL')
341
+        except ks_exceptions.EndpointNotFound:
342
+            url = CONF.murano.murano_url
343
+            LOG.warning("Murano endpoint not found in Keystone. "
344
+                        "Using CONF.")
345
+        return url if 'v1' not in url else "/".join(
346
+            url.split('/')[:url.split('/').index('v1')])
347
+
348
+    @classmethod
349
+    def verify_connection(cls, ip, port):
350
+        """Try to connect to specific ip:port with telnet.
351
+
352
+        :param ip: Ip that you want to check
353
+        :param port: Port that you want to check
354
+        :return: :raise RuntimeError:
355
+        """
356
+        tn = telnetlib.Telnet(ip, port)
357
+        tn.write('GET / HTTP/1.0\n\n')
358
+        try:
359
+            buf = tn.read_all()
360
+            LOG.debug('Data:\n {data}'.format(data=buf))
361
+            if len(buf) != 0:
362
+                tn.sock.sendall(telnetlib.IAC + telnetlib.NOP)
363
+                return
364
+            else:
365
+                raise RuntimeError('Resource at {0}:{1} not exist'.
366
+                                   format(ip, port))
367
+        except socket.error as e:
368
+            LOG.error('Socket Error: {error}'.format(error=e))
369
+
370
+    @classmethod
371
+    def get_ip_by_appname(cls, environment, appname):
372
+        """Returns ip of instance with a deployed application using app name.
373
+
374
+        :param environment: Murano environment
375
+        :param appname: Application name or substring of application name
376
+        :return:
377
+        """
378
+        for service in environment.services:
379
+            if appname in service['name']:
380
+                return service['instance']['floatingIpAddress']
381
+
382
+    @classmethod
383
+    def get_ip_by_instance_name(cls, environment, inst_name):
384
+        """Returns ip of instance using instance name.
385
+
386
+        :param environment: Murano environment
387
+        :param name: String, which is substring of name of instance or name of
388
+        instance
389
+        :return:
390
+        """
391
+        for service in environment.services:
392
+            if inst_name in service['instance']['name']:
393
+                return service['instance']['floatingIpAddress']
394
+
395
+    @classmethod
396
+    def get_k8s_ip_by_instance_name(cls, environment, inst_name, service_name):
397
+        """Returns ip of specific kubernetes node (gateway, master, minion).
398
+
399
+        Search depends on service name of kubernetes and names of spawned
400
+        instances
401
+        :param environment: Murano environment
402
+        :param inst_name: Name of instance or substring of instance name
403
+        :param service_name: Name of Kube Cluster application in Murano
404
+        environment
405
+        :return: Ip of Kubernetes instances
406
+        """
407
+        for service in environment.services:
408
+            if service_name in service['name']:
409
+                if "gateway" in inst_name:
410
+                    for gateway in service['gatewayNodes']:
411
+                        if inst_name in gateway['instance']['name']:
412
+                            LOG.debug(gateway['instance']['floatingIpAddress'])
413
+                            return gateway['instance']['floatingIpAddress']
414
+                elif "master" in inst_name:
415
+                    LOG.debug(service['masterNode']['instance'][
416
+                        'floatingIpAddress'])
417
+                    return service['masterNode']['instance'][
418
+                        'floatingIpAddress']
419
+                elif "minion" in inst_name:
420
+                    for minion in service['minionNodes']:
421
+                        if inst_name in minion['instance']['name']:
422
+                            LOG.debug(minion['instance']['floatingIpAddress'])
423
+                            return minion['instance']['floatingIpAddress']
424
+
425
+# -----------------------------Cleanup methods---------------------------------
426
+
427
+    @classmethod
428
+    def purge_uploaded_packages(cls):
429
+        """Cleanup for uploaded packages."""
430
+        cls.init_list("_packages")
431
+        try:
432
+            for pkg in cls._packages:
433
+                with ignored(Exception):
434
+                    cls.murano_client().packages.delete(pkg.id)
435
+        finally:
436
+            cls._packages = []
437
+        cls.init_list("_package_files")
438
+        try:
439
+            for pkg_file in cls._package_files:
440
+                os.remove(pkg_file)
441
+        finally:
442
+            cls._package_files = []
443
+
444
+    @classmethod
445
+    def purge_environments(cls):
446
+        """Cleanup for created environments."""
447
+        cls.init_list("_environments")
448
+        try:
449
+            for env in cls._environments:
450
+                with ignored(Exception):
451
+                    LOG.debug('Processing cleanup for environment {0} ({1})'.
452
+                              format(env.name, env.id))
453
+                    cls.environment_delete(env.id)
454
+                    cls.purge_stacks(env.id)
455
+                    time.sleep(5)
456
+        finally:
457
+            cls._environments = []
458
+
459
+    @classmethod
460
+    def purge_stacks(cls, environment_id):
461
+        stack = cls._get_stack(environment_id)
462
+        if not stack:
463
+            return
464
+        else:
465
+            cls.heat_client().stacks.delete(stack.id)
466
+
467
+# -----------------------Methods for environment CRUD--------------------------
468
+
469
+    @classmethod
470
+    def create_environment(cls, name=None):
471
+        """Creates Murano environment with random name.
472
+
473
+
474
+        :param name: Environment name
475
+        :return: Murano environment
476
+        """
477
+        if not name:
478
+            name = cls.rand_name('MuranoTe')
479
+        environment = cls.murano_client().environments.create({'name': name})
480
+        cls._environments.append(environment)
481
+        return environment
482
+
483
+    @classmethod
484
+    def get_environment(cls, environment):
485
+        """Refresh <Environment> variable.
486
+
487
+        :param environment: Murano environment.
488
+        :return: Murano environment.
489
+        """
490
+        return cls.murano_client().environments.get(environment.id)
491
+
492
+    @classmethod
493
+    def environment_delete(cls, environment_id, timeout=180):
494
+        """Remove Murano environment.
495
+
496
+        :param environment_id: ID of Murano environment
497
+        :param timeout: Timeout to environment get deleted
498
+        :return: :raise RuntimeError:
499
+        """
500
+        try:
501
+            cls.murano_client().environments.delete(environment_id)
502
+
503
+            start_time = time.time()
504
+            while time.time() - start_time < timeout:
505
+                try:
506
+                    cls.murano_client().environments.get(environment_id)
507
+                except exceptions.HTTPNotFound:
508
+                    LOG.debug('Environment with id {0} successfully deleted.'.
509
+                              format(environment_id))
510
+                    return
511
+            err_msg = ('Environment {0} was not deleted in {1} seconds'.
512
+                       format(environment_id, timeout))
513
+            LOG.error(err_msg)
514
+            raise RuntimeError(err_msg)
515
+        except Exception as exc:
516
+            LOG.debug('Environment with id {0} going to be abandoned.'.
517
+                      format(environment_id))
518
+            LOG.exception(exc)
519
+            cls.murano_client().environments.delete(environment_id,
520
+                                                    abandon=True)
521
+
522
+# -----------------------Methods for session actions---------------------------
523
+
524
+    @classmethod
525
+    def create_session(cls, environment):
526
+        return cls.murano_client().sessions.configure(environment.id)
527
+
528
+    @classmethod
529
+    def delete_session(cls, environment, session):
530
+        return cls.murano_client().sessions.delete(environment.id, session.id)
531
+
532
+
533
+# -------------------------------Heat methods----------------------------------
534
+
535
+    @classmethod
536
+    def _get_stack(cls, environment_id):
537
+
538
+        for stack in cls.heat_client().stacks.list():
539
+            stack_description = (
540
+                cls.heat_client().stacks.get(stack.id).description)
541
+            if not stack_description:
542
+                err_msg = ("Stack {0} description is empty".format(stack.id))
543
+                LOG.error(err_msg)
544
+                raise RuntimeError(err_msg)
545
+            if environment_id in stack_description:
546
+                return stack
547
+
548
+    @classmethod
549
+    def get_stack_template(cls, stack):
550
+        return cls.heat_client().stacks.template(stack.stack_name)

+ 30
- 0
murano_tempest_tests/tests/functional/common/zip_utils_mixin.py View File

@@ -0,0 +1,30 @@
1
+# Copyright (c) 2015 OpenStack Foundation
2
+#
3
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
4
+# not use this file except in compliance with the License. You may obtain
5
+# a copy of the License at
6
+#
7
+# http://www.apache.org/licenses/LICENSE-2.0
8
+#
9
+# Unless required by applicable law or agreed to in writing, software
10
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12
+# License for the specific language governing permissions and limitations
13
+# under the License.
14
+
15
+import os
16
+import zipfile
17
+
18
+
19
+class ZipUtilsMixin(object):
20
+    @staticmethod
21
+    def zip_dir(parent_dir, dir):
22
+        abs_path = os.path.join(parent_dir, dir)
23
+        path_len = len(abs_path) + 1
24
+        zip_file = abs_path + ".zip"
25
+        with zipfile.ZipFile(zip_file, "w") as zf:
26
+            for dir_name, _, files in os.walk(abs_path):
27
+                for filename in files:
28
+                    fn = os.path.join(dir_name, filename)
29
+                    zf.write(fn, fn[path_len:])
30
+        return zip_file

+ 0
- 0
murano_tempest_tests/tests/functional/integration/__init__.py View File


+ 157
- 0
murano_tempest_tests/tests/functional/integration/integration_base.py View File

@@ -0,0 +1,157 @@
1
+# Copyright (c) 2015 OpenStack Foundation, Inc.
2
+#
3
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
4
+# not use this file except in compliance with the License. You may obtain
5
+# a copy of the License at
6
+#
7
+# http://www.apache.org/licenses/LICENSE-2.0
8
+#
9
+# Unless required by applicable law or agreed to in writing, software
10
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12
+# License for the specific language governing permissions and limitations
13
+# under the License.
14
+
15
+import os
16
+import uuid
17
+
18
+from keystoneclient import exceptions as keystone_exceptions
19
+import mistralclient.api.client as mistralclient
20
+import testresources
21
+import testtools
22
+
23
+import murano_tempest_tests.tests.functional.common.tempest_utils \
24
+    as tempest_utils
25
+import murano_tempest_tests.tests.functional.common.utils as utils
26
+
27
+
28
+class MistralIntegration(testtools.TestCase, testtools.testcase.WithAttributes,
29
+                         testresources.ResourcedTestCase,
30
+                         tempest_utils.TempestDeployTestMixin):
31
+
32
+    @classmethod
33
+    @utils.memoize
34
+    def mistral_client(cls):
35
+        keystone_client = cls.keystone_client()
36
+
37
+        endpoint_type = 'publicURL'
38
+        service_type = 'workflowv2'
39
+
40
+        mistral_url = keystone_client.service_catalog.url_for(
41
+            service_type=service_type,
42
+            endpoint_type=endpoint_type)
43
+
44
+        auth_token = keystone_client.auth_token
45
+
46
+        return mistralclient.client(mistral_url=mistral_url,
47
+                                    auth_url=keystone_client.auth_url,
48
+                                    project_id=keystone_client.tenant_id,
49
+                                    endpoint_type=endpoint_type,
50
+                                    service_type=service_type,
51
+                                    auth_token=auth_token,
52
+                                    user_id=keystone_client.user_id)
53
+
54
+    @classmethod
55
+    def upload_mistral_showcase_app(cls):
56
+        app_dir = 'io.murano.apps.test.MistralShowcaseApp'
57
+        zip_file_path = cls.zip_dir(os.path.dirname(__file__), app_dir)
58
+        cls.init_list("_package_files")
59
+        cls._package_files.append(zip_file_path)
60
+        return cls.upload_package(
61
+            'MistralShowcaseApp',
62
+            {"categories": ["Web"], "tags": ["tag"]},
63
+            zip_file_path)
64
+
65
+    @staticmethod
66
+    def _create_env_body():
67
+        return {
68
+            "name": "Mistral_environment",
69
+            "?": {
70
+                "type": "io.murano.apps.test.MistralShowcaseApp",
71
+                "id": str(uuid.uuid4())
72
+            }
73
+        }
74
+
75
+
76
+class CongressIntegration(testtools.TestCase,
77
+                          testtools.testcase.WithAttributes,
78
+                          testresources.ResourcedTestCase,
79
+                          tempest_utils.TempestDeployTestMixin):
80
+
81
+    @classmethod
82
+    def _create_policy_req(cls, policy_name):
83
+        return {'abbreviation': None, 'kind': None,
84
+                'name': policy_name,
85
+                'description': None}
86
+
87
+    @classmethod
88
+    def _upload_policy_enf_app(cls):
89
+        app_dir = 'io.murano.apps.test.PolicyEnforcementTestApp'
90
+        zip_file_path = cls.zip_dir(os.path.dirname(__file__), app_dir)
91
+        cls.init_list("_package_files")
92
+        cls._package_files.append(zip_file_path)
93
+        return cls.upload_package(
94
+            'PolicyEnforcementTestApp',
95
+            {"categories": ["Web"], "tags": ["tag"]},
96
+            zip_file_path)
97
+
98
+    @classmethod
99
+    def _create_policy(cls, policy_names, kind=None):
100
+        for name in policy_names:
101
+            policy_req = {"name": name}
102
+            if kind:
103
+                policy_req["kind"] = kind
104
+            with utils.ignored(keystone_exceptions.Conflict):
105
+                cls.congress_client().create_policy(policy_req)
106
+
107
+            rules = []
108
+            rules_file = os.path.join(
109
+                os.path.dirname(__file__),
110
+                "rules_" + name + ".txt")
111
+
112
+            if os.path.isfile(rules_file):
113
+                with open(rules_file) as f:
114
+                    rules = [rule.strip() for rule in f.readlines()
115
+                             if rule.strip()]
116
+            for rule in rules:
117
+                with utils.ignored(keystone_exceptions.Conflict):
118
+                    cls.congress_client().create_policy_rule(name,
119
+                                                             {'rule': rule})
120
+
121
+    def _create_test_app(self, flavor, key):
122
+        """Application create request body
123
+
124
+        Deployment is expected to fail earlier due to policy violation.
125
+        Not existing image prevents real deployment to happen
126
+        in case that test goes wrong way.
127
+
128
+        :param flavor: instance image flavor
129
+        :param key: key name
130
+        """
131
+
132
+        return {
133
+            "instance": {
134
+                "flavor": flavor,
135
+                "keyname": key,
136
+                "image": "not_existing_image",
137
+                "assignFloatingIp": True,
138
+                "?": {
139
+                    "type": "io.murano.resources.LinuxMuranoInstance",
140
+                    "id": str(uuid.uuid4())
141
+                },
142
+                "name": "testMurano"
143
+            },
144
+            "name": "teMurano",
145
+            "?": {
146
+                "type": "io.murano.apps.test.PolicyEnforcementTestApp",
147
+                "id": str(uuid.uuid4())
148
+            }
149
+        }
150
+
151
+    def _check_deploy_failure(self, post_body, expected_text):
152
+        environment_name = 'PolicyEnfTestEnv' + uuid.uuid4().hex[:5]
153
+        env = self.deploy_apps(environment_name, post_body)
154
+        status = self.wait_for_final_status(env)
155
+        self.assertIn("failure", status[0], "Unexpected status : " + status[0])
156
+        self.assertIn(expected_text, status[1].lower(),
157
+                      "Unexpected status : " + status[1])

+ 32
- 0
murano_tempest_tests/tests/functional/integration/io.murano.apps.test.MistralShowcaseApp/Classes/MistralShowcaseApp.yaml View File

@@ -0,0 +1,32 @@
1
+Namespaces:
2
+  =: io.murano.apps.test
3
+  std: io.murano
4
+  sys: io.murano.system
5
+
6
+
7
+Name: MistralShowcaseApp
8
+
9
+Extends: std:Application
10
+
11
+Properties:
12
+  name:
13
+    Contract: $.string().notNull()
14
+
15
+  mistralClient:
16
+    Contract: $.class(sys:MistralClient)
17
+    Usage: Runtime
18
+
19
+
20
+Methods:
21
+  initialize:
22
+    Body:
23
+      - $environment: $.find(std:Environment).require()
24
+      - $this.mistralClient: new(sys:MistralClient, $environment)
25
+
26
+  deploy:
27
+    Body:
28
+      - $resources: new('io.murano.system.Resources')
29
+      - $workflow: $resources.string('TestEcho_MistralWorkflow.yaml')
30
+      - $.mistralClient.upload(definition => $workflow)
31
+      - $output: $.mistralClient.run(name => 'test_echo', inputs => dict(input_1 => input_1_value))
32
+      - $this.find(std:Environment).reporter.report($this, $output.get('out_3'))

+ 24
- 0
murano_tempest_tests/tests/functional/integration/io.murano.apps.test.MistralShowcaseApp/Resources/TestEcho_MistralWorkflow.yaml View File

@@ -0,0 +1,24 @@
1
+version: '2.0'
2
+
3
+test_echo:
4
+  type: direct
5
+  input:
6
+    - input_1
7
+  output:
8
+    out_1: <% $.task1_output_1 %>
9
+    out_2: <% $.task2_output_2 %>
10
+    out_3: <% $.input_1 %>
11
+  tasks:
12
+    my_echo_test:
13
+      action: std.echo output='just a string'
14
+      publish:
15
+        task1_output_1: 'task1_output_1_value'
16
+        task1_output_2: 'task1_output_2_value'
17
+      on-success:
18
+        - my_echo_test_2
19
+
20
+    my_echo_test_2:
21
+      action: std.echo output='just a string'
22
+      publish:
23
+        task2_output_1: 'task2_output_1_value'
24
+        task2_output_2: 'task2_output_2_value'

+ 10
- 0
murano_tempest_tests/tests/functional/integration/io.murano.apps.test.MistralShowcaseApp/manifest.yaml View File

@@ -0,0 +1,10 @@
1
+Format: 1.0
2
+Type: Application
3
+FullName: io.murano.apps.test.MistralShowcaseApp
4
+Name: MistralShowcaseApp
5
+Description: |
6
+ MistralShowcaseApp.
7
+Author: 'Mirantis, Inc'
8
+Tags: [Servlets, Server, Pages, Java]
9
+Classes:
10
+ io.murano.apps.test.MistralShowcaseApp: MistralShowcaseApp.yaml

+ 48
- 0
murano_tempest_tests/tests/functional/integration/io.murano.apps.test.PolicyEnforcementTestApp/Classes/PolicyEnforcementTestApp.yaml View File

@@ -0,0 +1,48 @@
1
+Namespaces:
2
+  =: io.murano.apps.test
3
+  std: io.murano
4
+  res: io.murano.resources
5
+  sys: io.murano.system
6
+
7
+
8
+Name: PolicyEnforcementTestApp
9
+
10
+Extends: std:Application
11
+
12
+Properties:
13
+  name:
14
+    Contract: $.string().notNull()
15
+
16
+  instance:
17
+    Contract: $.class(res:Instance).notNull()
18
+
19
+  host:
20
+    Contract: $.string()
21
+    Usage: Out
22
+
23
+  user:
24
+    Contract: $.string()
25
+    Usage: Out
26
+
27
+Methods:
28
+  initialize:
29
+    Body:
30
+      - $._environment: $.find(std:Environment).require()
31
+
32
+  deploy:
33
+    Body:
34
+      - If: not $.getAttr(deployed, false)
35
+        Then:
36
+          - $._environment.reporter.report($this, 'Creating VM')
37
+          - $securityGroupIngress:
38
+              - ToPort: 22
39
+                FromPort: 22
40
+                IpProtocol: tcp
41
+                External: true
42
+          - $._environment.securityGroupManager.addGroupIngress($securityGroupIngress)
43
+          - $.instance.deploy()
44
+          - $resources: new(sys:Resources)
45
+          - $._environment.reporter.report($this, 'Test VM is installed')
46
+          - $.host: $.instance.ipAddresses[0]
47
+          - $.user: 'root'
48
+          - $.setAttr(deployed, true)

+ 10
- 0
murano_tempest_tests/tests/functional/integration/io.murano.apps.test.PolicyEnforcementTestApp/manifest.yaml View File

@@ -0,0 +1,10 @@
1
+Format: 1.0
2
+Type: Application
3
+FullName: io.murano.apps.test.PolicyEnforcementTestApp
4
+Name: PolicyEnforcementTestApp
5
+Description: |
6
+ This is a simple test app with a single VM for policy enforcement testing purposes.
7
+Author: 'Hewlett-Packard'
8
+Tags: [test]
9
+Classes:
10
+ io.murano.apps.test.PolicyEnforcementTestApp: PolicyEnforcementTestApp.yaml

+ 18
- 0
murano_tempest_tests/tests/functional/integration/rules_murano_action.txt View File

@@ -0,0 +1,18 @@
1
+action("deleteEnv")
2
+
3
+murano:states-(eid,st) :- deleteEnv(eid), murano:states( eid, st)
4
+
5
+murano:parent_types-(tid, type) :- deleteEnv(eid), murano:connected(eid, tid),murano:parent_types(tid,type)
6
+murano:parent_types-(eid, type) :- deleteEnv(eid), murano:parent_types(eid,type)
7
+
8
+murano:properties-(oid, pn, pv) :- deleteEnv(eid), murano:connected( eid, oid),murano:properties(oid, pn, pv)
9
+murano:properties-(eid, pn, pv) :- deleteEnv(eid), murano:properties(eid, pn, pv)
10
+
11
+murano:objects-(oid, pid, ot) :- deleteEnv(eid), murano:connected(eid, oid), murano:objects(oid, pid, ot)
12
+murano:objects-(eid, tnid, ot) :- deleteEnv(eid), murano:objects(eid, tnid, ot)
13
+
14
+murano:relationships-(sid,tid, rt) :- deleteEnv(eid), murano:connected( eid, sid), murano:relationships( sid, tid, rt)
15
+murano:relationships-(eid,tid, rt) :- deleteEnv(eid), murano:relationships( eid, tid, rt)
16
+
17
+murano:connected-(tid, tid2) :- deleteEnv(eid), murano:connected(eid, tid), murano:connected(tid,tid2)
18
+murano:connected-(eid,tid) :- deleteEnv(eid), murano:connected(eid,tid)

+ 7
- 0
murano_tempest_tests/tests/functional/integration/rules_murano_system.txt View File

@@ -0,0 +1,7 @@
1
+missing_key("")
2
+invalid_flavor_name("really.bad.flavor")
3
+predeploy_errors(eid, obj_id, msg):-murano:objects(obj_id, pid, type), murano_env_of_object(obj_id, eid), murano:properties(obj_id, "flavor", flavor_name), invalid_flavor_name(flavor_name), murano:properties(obj_id, "name", obj_name), concat(obj_name, ": bad flavor", msg)
4
+predeploy_errors(eid, obj_id, msg):-murano:objects(obj_id, pid, type), murano_env_of_object(obj_id, eid), murano:properties(obj_id, "keyname", key_name), missing_key(key_name), murano:properties(obj_id, "name", obj_name), concat(obj_name, ": missing key", msg)
5
+murano_env_of_object(oid,eid):-murano:connected(eid,oid), murano:objects(eid,tid,"io.murano.Environment")
6
+bad_flavor_synonyms("horrible.flavor")
7
+predeploy_modify(eid, obj_id, action):-murano:objects(obj_id, pid, type), murano_env_of_object(obj_id, eid), murano:properties(obj_id, "flavor", flavor_name), bad_flavor_synonyms(flavor_name), concat("set-property: {object_id: ", obj_id, first_part ),  concat(first_part, ", prop_name: flavor, value: really.bad.flavor}", action)

+ 62
- 0
murano_tempest_tests/tests/functional/integration/test_mistral.py View File

@@ -0,0 +1,62 @@
1
+# Copyright (c) 2015 OpenStack Foundation
2
+#
3
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
4
+# not use this file except in compliance with the License. You may obtain
5
+# a copy of the License at
6
+#
7
+# http://www.apache.org/licenses/LICENSE-2.0
8
+#
9
+# Unless required by applicable law or agreed to in writing, software
10
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12
+# License for the specific language governing permissions and limitations
13
+# under the License.
14
+
15
+import uuid
16
+
17
+from nose.plugins.attrib import attr as tag
18
+
19
+import murano_tempest_tests.tests.functional.common.utils as common_utils
20
+import murano_tempest_tests.tests.functional.integration.integration_base \
21
+    as core
22
+
23
+
24
+class MistralTest(core.MistralIntegration):
25
+
26
+    @classmethod
27
+    def setUpClass(cls):
28
+        super(MistralTest, cls).setUpClass()
29
+
30
+        try:
31
+            # Upload the Murano test package.
32
+            cls.upload_mistral_showcase_app()
33
+
34
+        except Exception:
35
+            cls.tearDownClass()
36
+            raise
37
+
38
+    @classmethod
39
+    def tearDownClass(cls):
40
+        with common_utils.ignored(Exception):
41
+            cls.purge_environments()
42
+        with common_utils.ignored(Exception):
43
+            cls.purge_uploaded_packages()
44
+
45
+    @tag('all', 'coverage')
46
+    def test_deploy_package_success(self):
47
+        # Test expects successful deployment and one output: input_1_value.
48
+
49
+        # Create env json string.
50
+        post_body = self._create_env_body()
51
+
52
+        environment_name = 'Mistral_environment' + uuid.uuid4().hex[:5]
53
+
54
+        # Deploy the environment.
55
+        env = self.deploy_apps(environment_name, post_body)
56
+
57
+        status = self.wait_for_final_status(env)
58
+
59
+        self.assertIn("ready", status[0],
60
+                      "Unexpected status : " + status[0])
61
+        self.assertIn("input_1_value", status[1],
62
+                      "Unexpected output value: " + status[1])

+ 89
- 0
murano_tempest_tests/tests/functional/integration/test_policy_enf.py View File

@@ -0,0 +1,89 @@
1
+# Copyright (c) 2015 OpenStack Foundation
2
+#
3
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
4
+# not use this file except in compliance with the License. You may obtain
5
+# a copy of the License at
6
+#
7
+# http://www.apache.org/licenses/LICENSE-2.0
8
+#
9
+# Unless required by applicable law or agreed to in writing, software
10
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12
+# License for the specific language governing permissions and limitations
13
+# under the License.
14
+
15
+import muranoclient.common.exceptions as murano_exceptions
16
+from nose.plugins.attrib import attr as tag
17
+
18
+import murano_tempest_tests.tests.functional.common.utils as common_utils
19
+import murano_tempest_tests.tests.functional.integration.integration_base \
20
+    as core
21
+
22
+
23
+class PolicyEnforcementTest(core.CongressIntegration):
24
+
25
+    @classmethod
26
+    def setUpClass(cls):
27
+        super(PolicyEnforcementTest, cls).setUpClass()
28
+
29
+        cls._create_policy(["murano", "murano_system"])
30
+        cls._create_policy(["murano_action"], kind="action")
31
+
32
+        with common_utils.ignored(murano_exceptions.HTTPInternalServerError):
33
+            cls._upload_policy_enf_app()
34
+
35
+    @classmethod
36
+    def tearDownClass(cls):
37
+        cls.purge_uploaded_packages()
38
+
39
+    def tearDown(self):
40
+        super(PolicyEnforcementTest, self).tearDown()
41
+        self.purge_environments()
42
+
43
+    @tag('all', 'coverage')
44
+    def test_deploy_policy_fail_key(self):
45
+        """Test expects failure due to empty key name.
46
+
47
+        In rules_murano_system.txt file are defined congress
48
+        rules preventing deploy environment where instances
49
+        have empty keyname property. In other words admin
50
+        prevented spawn instance without assigned key pair.
51
+        """
52
+
53
+        self._check_deploy_failure(
54
+            self._create_test_app(key='',
55
+                                  flavor='m1.small'),
56
+            'missing key')
57
+
58
+    @tag('all', 'coverage')
59
+    def test_deploy_policy_fail_flavor(self):
60
+        """Test expects failure due to blacklisted flavor
61
+
62
+        In rules_murano_system.txt file are defined congress
63
+        rules preventing deploy environment where instances
64
+        have flavor property set to 'really.bad.flavor'.
65
+        """
66
+
67
+        self._check_deploy_failure(
68
+            self._create_test_app(flavor='really.bad.flavor',
69
+                                  key='test-key'),
70
+            'bad flavor')
71
+
72
+    @tag('all', 'coverage')
73
+    def test_set_property_policy(self):
74
+        """Tests environment modification by policy
75
+
76
+        In rules_murano_system.txt file are defined congress
77
+        rules changing flavor property. There are defined
78
+        synonyms for 'really.bad.flavor'. One of such synonyms
79
+        is 'horrible.flavor' Environment is modified prior deployment.
80
+        The synonym name 'horrible.flavor' is set to original
81
+        value 'really.bad.flavor' and then deployment is aborted
82
+        because instances of 'really.bad.flavor' are prevented
83
+        to be deployed like for the test above.
84
+        """
85
+
86
+        self._check_deploy_failure(
87
+            self._create_test_app(key="test-key",
88
+                                  flavor="horrible.flavor"),
89
+            "bad flavor")

+ 9
- 0
requirements.txt View File

@@ -10,3 +10,12 @@ oslo.utils>=3.33.0 # Apache-2.0
10 10
 testtools>=2.2.0 # MIT
11 11
 tempest>=17.1.0 # Apache-2.0
12 12
 requests>=2.14.2 # Apache-2.0
13
+nose>=1.3.7 # LGPL
14
+testresources>=2.0.0 # Apache-2.0/BSD
15
+
16
+python-keystoneclient>=3.8.0 # Apache-2.0
17
+python-heatclient>=1.10.0 # Apache-2.0
18
+python-neutronclient>=6.7.0 # Apache-2.0
19
+python-muranoclient>=0.8.2 # Apache-2.0
20
+python-congressclient<2000,>=1.9.0 # Apache-2.0
21
+python-mistralclient!=3.2.0,>=3.1.0 # Apache-2.0

Loading…
Cancel
Save