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

This commit is contained in:
Zuul 2018-08-01 13:09:12 +00:00 committed by Gerrit Code Review
commit 7a908fff2c
17 changed files with 1095 additions and 0 deletions

View File

@ -0,0 +1,49 @@
# Copyright (c) 2015 OpenStack Foundation
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import congressclient.v1.client as cclient
from keystoneauth1 import identity
from keystoneauth1 import session as ksasession
import keystoneclient.v3 as ksclient
from tempest import config
import murano_tempest_tests.tests.functional.common.utils as common_utils
CONF = config.CONF
class TempestDeployTestMixin(common_utils.DeployTestMixin):
"""Overrides methods to use tempest configuration."""
@staticmethod
@common_utils.memoize
def keystone_client():
return ksclient.Client(username=CONF.auth.admin_username,
password=CONF.auth.admin_password,
tenant_name=CONF.auth.admin_project_name,
auth_url=CONF.identity.uri_v3)
@staticmethod
@common_utils.memoize
def congress_client():
auth = identity.v3.Password(
auth_url=CONF.identity.uri_v3,
username=CONF.auth.admin_username,
password=CONF.auth.admin_password,
project_name=CONF.auth.admin_project_name,
user_domain_name=CONF.auth.admin_domain_name,
project_domain_name=CONF.auth.admin_domain_name)
session = ksasession.Session(auth=auth)
return cclient.Client(session=session,
service_type='policy')

View File

@ -0,0 +1,550 @@
# Copyright (c) 2015 OpenStack Foundation
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import collections
import contextlib
import json
import os
import random
import socket
import telnetlib
import time
from heatclient import client as heatclient
from keystoneclient import exceptions as ks_exceptions
import keystoneclient.v3 as ksclient
from muranoclient import client as mclient
import muranoclient.common.exceptions as exceptions
from muranoclient.glance import client as glare_client
from oslo_log import log as logging
from tempest import config
import yaml
import murano_tempest_tests.tests.functional.common.zip_utils_mixin \
as zip_utils
CONF = config.CONF
LOG = logging.getLogger(__name__)
SessionState = collections.namedtuple('SessionState', [
'OPENED', 'DEPLOYING', 'DEPLOYED', 'DEPLOY_FAILURE', 'DELETING',
'DELETE_FAILURE'
])(
OPENED='opened',
DEPLOYING='deploying',
DEPLOYED='deployed',
DEPLOY_FAILURE='deploy failure',
DELETING='deleting',
DELETE_FAILURE='delete failure'
)
@contextlib.contextmanager
def ignored(*exceptions):
try:
yield
except exceptions:
pass
def memoize(f):
"""Saves result of decorated function to cache
Decorator, which saves result of a decorated function
to cache.
TTL for cache is 1800 sec
:param f: decorated function
:return: saved result of a decorated function
"""
cache = {}
def decorated_function(*args):
if args in cache:
if time.time() - cache[args][1] < 1800:
return cache[args][0]
else:
cache[args] = (f(*args), time.time())
return cache[args][0]
else:
cache[args] = (f(*args), time.time())
return cache[args][0]
return decorated_function
class DeployTestMixin(zip_utils.ZipUtilsMixin):
@staticmethod
@memoize
def keystone_client():
return ksclient.Client(username=CONF.auth.admin_username,
password=CONF.auth.admin_password,
tenant_name=CONF.auth.admin_project_name,
auth_url=CONF.identity.uri_v3)
@classmethod
@memoize
def heat_client(cls):
heat_url = cls.keystone_client().service_catalog.url_for(
service_type='orchestration', endpoint_type='publicURL')
return heatclient.Client('1',
endpoint=heat_url,
token=cls.keystone_client().auth_token)
@classmethod
@memoize
def murano_client(cls):
murano_url = cls.get_murano_url()
if CONF.application_catalog.glare_backend:
glare_endpoint = "http://127.0.0.1:9494"
artifacts_client = glare_client.Client(
endpoint=glare_endpoint,
token=cls.keystone_client().auth_token,
insecure=False, key_file=None, ca_file=None, cert_file=None,
type_name="murano", type_version=1)
else:
artifacts_client = None
return mclient.Client('1',
artifacts_client=artifacts_client,
endpoint=murano_url,
token=cls.keystone_client().auth_token)
# --------------------------Specific test methods------------------------------
@classmethod
def deploy_apps(cls, name, *apps):
"""Create and deploy environment.
:param name: Murano environment name
:param apps: App(s), described in JSON format
:return: Murano environment
"""
environment = cls.murano_client().environments.create({'name': name})
cls.init_list("_environments")
cls._environments.append(environment)
session = cls.murano_client().sessions.configure(environment.id)
for app in apps:
cls.murano_client().services.post(
environment.id,
path='/',
data=app,
session_id=session.id)
cls.murano_client().sessions.deploy(environment.id, session.id)
return environment
@classmethod
def wait_for_final_status(cls, environment, timeout=300):
"""Function for wait final status of environment.
:param environment: Murano environment.
:param timeout: Timeout for waiting environment to get any status
excluding DEPLOYING state
"""
start_time = time.time()
status = environment.manager.get(environment.id).status
while SessionState.DEPLOYING == status:
if time.time() - start_time > timeout:
err_msg = ('Deployment not finished in {amount} seconds'
.format(amount=timeout))
LOG.error(err_msg)
raise RuntimeError(err_msg)
time.sleep(5)
status = environment.manager.get(environment.id).status
dep = cls.murano_client().deployments.list(environment.id)
reports = cls.murano_client().deployments.reports(environment.id,
dep[0].id)
return status, ", ".join([r.text for r in reports])
# -----------------------------Reports methods---------------------------------
@classmethod
def get_last_deployment(cls, environment):
"""Gets last deployment of Murano environment.
:param environment: Murano environment
:return:
"""
deployments = cls.murano_client().deployments.list(environment.id)
return deployments[0]
@classmethod
def get_deployment_report(cls, environment, deployment):
"""Gets reports for environment with specific deployment.
:param environment: Murano environment.
:param deployment: Murano deployment for certain environment
:return:
"""
history = ''
report = cls.murano_client().deployments.reports(
environment.id, deployment.id)
for status in report:
history += '\t{0} - {1}\n'.format(status.created, status.text)
return history
@classmethod
def _log_report(cls, environment):
"""Used for logging reports on failures.
:param environment: Murano environment.
"""
deployment = cls.get_last_deployment(environment)
try:
details = deployment.result['result']['details']
LOG.warning('Details:\n {details}'.format(details=details))
except Exception as e:
LOG.error(e)
report = cls.get_deployment_report(environment, deployment)
LOG.debug('Report:\n {report}\n'.format(report=report))
# -----------------------------Service methods---------------------------------
@classmethod
def add_service(cls, environment, data, session, to_dict=False):
"""This function adds a specific service to environment.
:param environment: Murano environment
:param data: JSON with specific servive to add into
:param session: Session that is open for environment
:param to_dict: If True - returns a JSON object with service
If False - returns a specific class <Service>
"""
LOG.debug('Added service:\n {data}'.format(data=data))
service = cls.murano_client().services.post(environment.id,
path='/', data=data,
session_id=session.id)
if to_dict:
return cls._convert_service(service)
else:
return service
@classmethod
def services_list(cls, environment):
"""Get a list of environment services.
:param environment: Murano environment
:return: List of <Service> objects
"""
return cls.murano_client().services.list(environment.id)
@classmethod
def get_service(cls, environment, service_name, to_dict=True):
"""Get a service with specific name from environment.
:param to_dict: Convert service to JSON or not to convert
:param environment: Murano environment
:param service_name: Service name
:return: JSON or <Service> object
"""
for service in cls.services_list(environment):
if service.name == service_name:
return cls._convert_service(service) if to_dict else service
@classmethod
def _convert_service(cls, service):
"""Converts a <Service> to JSON object.
:param service: <Service> object
:return: JSON object
"""
component = service.to_dict()
component = json.dumps(component)
return yaml.safe_load(component)
@classmethod
def get_service_id(cls, service):
"""Gets id on <Service> object.
:param service: <Service> object
:return: ID of the Service
"""
serv = cls._convert_service(service)
serv_id = serv['?']['id']
return serv_id
@classmethod
def delete_service(cls, environment, session, service):
"""This function removes a specific service from environment.
:param environment: Murano environment
:param session: Session fir urano environment
:param service: <Service> object
:return: Updated murano environment
"""
cls.murano_client().services.delete(
environment.id, path='/{0}'.format(cls.get_service_id(service)),
session_id=session.id)
LOG.debug('Service with name {0} from environment {1} successfully '
'removed'.format(environment.name, service.name))
updated_env = cls.get_environment(environment)
return updated_env
# -----------------------------Packages methods--------------------------------
@classmethod
def upload_package(cls, package_name, body, app):
"""Uploads a .zip package with parameters to Murano.
:param package_name: Package name in Murano repository
:param body: Categories, tags, etc.
e.g. {
"categories": ["Application Servers"],
"tags": ["tag"]
}
:param app: Correct .zip archive with the application
:return: Package
"""
files = {'{0}'.format(package_name): open(app, 'rb')}
package = cls.murano_client().packages.create(body, files)
cls.init_list("_packages")
cls._packages.append(package)
return package
# ------------------------------Common methods---------------------------------
@classmethod
def rand_name(cls, name='murano'):
"""Generates random string.
:param name: Basic name
:return:
"""
return name + str(random.randint(1, 0x7fffffff))
@classmethod
def init_list(cls, list_name):
if not hasattr(cls, list_name):
setattr(cls, list_name, [])
@classmethod
def get_murano_url(cls):
try:
url = cls.keystone_client().service_catalog.url_for(
service_type='application-catalog', endpoint_type='publicURL')
except ks_exceptions.EndpointNotFound:
url = CONF.murano.murano_url
LOG.warning("Murano endpoint not found in Keystone. "
"Using CONF.")
return url if 'v1' not in url else "/".join(
url.split('/')[:url.split('/').index('v1')])
@classmethod
def verify_connection(cls, ip, port):
"""Try to connect to specific ip:port with telnet.
:param ip: Ip that you want to check
:param port: Port that you want to check
:return: :raise RuntimeError:
"""
tn = telnetlib.Telnet(ip, port)
tn.write('GET / HTTP/1.0\n\n')
try:
buf = tn.read_all()
LOG.debug('Data:\n {data}'.format(data=buf))
if len(buf) != 0:
tn.sock.sendall(telnetlib.IAC + telnetlib.NOP)
return
else:
raise RuntimeError('Resource at {0}:{1} not exist'.
format(ip, port))
except socket.error as e:
LOG.error('Socket Error: {error}'.format(error=e))
@classmethod
def get_ip_by_appname(cls, environment, appname):
"""Returns ip of instance with a deployed application using app name.
:param environment: Murano environment
:param appname: Application name or substring of application name
:return:
"""
for service in environment.services:
if appname in service['name']:
return service['instance']['floatingIpAddress']
@classmethod
def get_ip_by_instance_name(cls, environment, inst_name):
"""Returns ip of instance using instance name.
:param environment: Murano environment
:param name: String, which is substring of name of instance or name of
instance
:return:
"""
for service in environment.services:
if inst_name in service['instance']['name']:
return service['instance']['floatingIpAddress']
@classmethod
def get_k8s_ip_by_instance_name(cls, environment, inst_name, service_name):
"""Returns ip of specific kubernetes node (gateway, master, minion).
Search depends on service name of kubernetes and names of spawned
instances
:param environment: Murano environment
:param inst_name: Name of instance or substring of instance name
:param service_name: Name of Kube Cluster application in Murano
environment
:return: Ip of Kubernetes instances
"""
for service in environment.services:
if service_name in service['name']:
if "gateway" in inst_name:
for gateway in service['gatewayNodes']:
if inst_name in gateway['instance']['name']:
LOG.debug(gateway['instance']['floatingIpAddress'])
return gateway['instance']['floatingIpAddress']
elif "master" in inst_name:
LOG.debug(service['masterNode']['instance'][
'floatingIpAddress'])
return service['masterNode']['instance'][
'floatingIpAddress']
elif "minion" in inst_name:
for minion in service['minionNodes']:
if inst_name in minion['instance']['name']:
LOG.debug(minion['instance']['floatingIpAddress'])
return minion['instance']['floatingIpAddress']
# -----------------------------Cleanup methods---------------------------------
@classmethod
def purge_uploaded_packages(cls):
"""Cleanup for uploaded packages."""
cls.init_list("_packages")
try:
for pkg in cls._packages:
with ignored(Exception):
cls.murano_client().packages.delete(pkg.id)
finally:
cls._packages = []
cls.init_list("_package_files")
try:
for pkg_file in cls._package_files:
os.remove(pkg_file)
finally:
cls._package_files = []
@classmethod
def purge_environments(cls):
"""Cleanup for created environments."""
cls.init_list("_environments")
try:
for env in cls._environments:
with ignored(Exception):
LOG.debug('Processing cleanup for environment {0} ({1})'.
format(env.name, env.id))
cls.environment_delete(env.id)
cls.purge_stacks(env.id)
time.sleep(5)
finally:
cls._environments = []
@classmethod
def purge_stacks(cls, environment_id):
stack = cls._get_stack(environment_id)
if not stack:
return
else:
cls.heat_client().stacks.delete(stack.id)
# -----------------------Methods for environment CRUD--------------------------
@classmethod
def create_environment(cls, name=None):
"""Creates Murano environment with random name.
:param name: Environment name
:return: Murano environment
"""
if not name:
name = cls.rand_name('MuranoTe')
environment = cls.murano_client().environments.create({'name': name})
cls._environments.append(environment)
return environment
@classmethod
def get_environment(cls, environment):
"""Refresh <Environment> variable.
:param environment: Murano environment.
:return: Murano environment.
"""
return cls.murano_client().environments.get(environment.id)
@classmethod
def environment_delete(cls, environment_id, timeout=180):
"""Remove Murano environment.
:param environment_id: ID of Murano environment
:param timeout: Timeout to environment get deleted
:return: :raise RuntimeError:
"""
try:
cls.murano_client().environments.delete(environment_id)
start_time = time.time()
while time.time() - start_time < timeout:
try:
cls.murano_client().environments.get(environment_id)
except exceptions.HTTPNotFound:
LOG.debug('Environment with id {0} successfully deleted.'.
format(environment_id))
return
err_msg = ('Environment {0} was not deleted in {1} seconds'.
format(environment_id, timeout))
LOG.error(err_msg)
raise RuntimeError(err_msg)
except Exception as exc:
LOG.debug('Environment with id {0} going to be abandoned.'.
format(environment_id))
LOG.exception(exc)
cls.murano_client().environments.delete(environment_id,
abandon=True)
# -----------------------Methods for session actions---------------------------
@classmethod
def create_session(cls, environment):
return cls.murano_client().sessions.configure(environment.id)
@classmethod
def delete_session(cls, environment, session):
return cls.murano_client().sessions.delete(environment.id, session.id)
# -------------------------------Heat methods----------------------------------
@classmethod
def _get_stack(cls, environment_id):
for stack in cls.heat_client().stacks.list():
stack_description = (
cls.heat_client().stacks.get(stack.id).description)
if not stack_description:
err_msg = ("Stack {0} description is empty".format(stack.id))
LOG.error(err_msg)
raise RuntimeError(err_msg)
if environment_id in stack_description:
return stack
@classmethod
def get_stack_template(cls, stack):
return cls.heat_client().stacks.template(stack.stack_name)

View File

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

View File

@ -0,0 +1,157 @@
# Copyright (c) 2015 OpenStack Foundation, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import os
import uuid
from keystoneclient import exceptions as keystone_exceptions
import mistralclient.api.client as mistralclient
import testresources
import testtools
import murano_tempest_tests.tests.functional.common.tempest_utils \
as tempest_utils
import murano_tempest_tests.tests.functional.common.utils as utils
class MistralIntegration(testtools.TestCase, testtools.testcase.WithAttributes,
testresources.ResourcedTestCase,
tempest_utils.TempestDeployTestMixin):
@classmethod
@utils.memoize
def mistral_client(cls):
keystone_client = cls.keystone_client()
endpoint_type = 'publicURL'
service_type = 'workflowv2'
mistral_url = keystone_client.service_catalog.url_for(
service_type=service_type,
endpoint_type=endpoint_type)
auth_token = keystone_client.auth_token
return mistralclient.client(mistral_url=mistral_url,
auth_url=keystone_client.auth_url,
project_id=keystone_client.tenant_id,
endpoint_type=endpoint_type,
service_type=service_type,
auth_token=auth_token,
user_id=keystone_client.user_id)
@classmethod
def upload_mistral_showcase_app(cls):
app_dir = 'io.murano.apps.test.MistralShowcaseApp'
zip_file_path = cls.zip_dir(os.path.dirname(__file__), app_dir)
cls.init_list("_package_files")
cls._package_files.append(zip_file_path)
return cls.upload_package(
'MistralShowcaseApp',
{"categories": ["Web"], "tags": ["tag"]},
zip_file_path)
@staticmethod
def _create_env_body():
return {
"name": "Mistral_environment",
"?": {
"type": "io.murano.apps.test.MistralShowcaseApp",
"id": str(uuid.uuid4())
}
}
class CongressIntegration(testtools.TestCase,
testtools.testcase.WithAttributes,
testresources.ResourcedTestCase,
tempest_utils.TempestDeployTestMixin):
@classmethod
def _create_policy_req(cls, policy_name):
return {'abbreviation': None, 'kind': None,
'name': policy_name,
'description': None}
@classmethod
def _upload_policy_enf_app(cls):
app_dir = 'io.murano.apps.test.PolicyEnforcementTestApp'
zip_file_path = cls.zip_dir(os.path.dirname(__file__), app_dir)
cls.init_list("_package_files")
cls._package_files.append(zip_file_path)
return cls.upload_package(
'PolicyEnforcementTestApp',
{"categories": ["Web"], "tags": ["tag"]},
zip_file_path)
@classmethod
def _create_policy(cls, policy_names, kind=None):
for name in policy_names:
policy_req = {"name": name}
if kind:
policy_req["kind"] = kind
with utils.ignored(keystone_exceptions.Conflict):
cls.congress_client().create_policy(policy_req)
rules = []
rules_file = os.path.join(
os.path.dirname(__file__),
"rules_" + name + ".txt")
if os.path.isfile(rules_file):
with open(rules_file) as f:
rules = [rule.strip() for rule in f.readlines()
if rule.strip()]
for rule in rules:
with utils.ignored(keystone_exceptions.Conflict):
cls.congress_client().create_policy_rule(name,
{'rule': rule})
def _create_test_app(self, flavor, key):
"""Application create request body
Deployment is expected to fail earlier due to policy violation.
Not existing image prevents real deployment to happen
in case that test goes wrong way.
:param flavor: instance image flavor
:param key: key name
"""
return {
"instance": {
"flavor": flavor,
"keyname": key,
"image": "not_existing_image",
"assignFloatingIp": True,
"?": {
"type": "io.murano.resources.LinuxMuranoInstance",
"id": str(uuid.uuid4())
},
"name": "testMurano"
},
"name": "teMurano",
"?": {
"type": "io.murano.apps.test.PolicyEnforcementTestApp",
"id": str(uuid.uuid4())
}
}
def _check_deploy_failure(self, post_body, expected_text):
environment_name = 'PolicyEnfTestEnv' + uuid.uuid4().hex[:5]
env = self.deploy_apps(environment_name, post_body)
status = self.wait_for_final_status(env)
self.assertIn("failure", status[0], "Unexpected status : " + status[0])
self.assertIn(expected_text, status[1].lower(),
"Unexpected status : " + status[1])

View File

@ -0,0 +1,32 @@
Namespaces:
=: io.murano.apps.test
std: io.murano
sys: io.murano.system
Name: MistralShowcaseApp
Extends: std:Application
Properties:
name:
Contract: $.string().notNull()
mistralClient:
Contract: $.class(sys:MistralClient)
Usage: Runtime
Methods:
initialize:
Body:
- $environment: $.find(std:Environment).require()
- $this.mistralClient: new(sys:MistralClient, $environment)
deploy:
Body:
- $resources: new('io.murano.system.Resources')
- $workflow: $resources.string('TestEcho_MistralWorkflow.yaml')
- $.mistralClient.upload(definition => $workflow)
- $output: $.mistralClient.run(name => 'test_echo', inputs => dict(input_1 => input_1_value))
- $this.find(std:Environment).reporter.report($this, $output.get('out_3'))

View File

@ -0,0 +1,24 @@
version: '2.0'
test_echo:
type: direct
input:
- input_1
output:
out_1: <% $.task1_output_1 %>
out_2: <% $.task2_output_2 %>
out_3: <% $.input_1 %>
tasks:
my_echo_test:
action: std.echo output='just a string'
publish:
task1_output_1: 'task1_output_1_value'
task1_output_2: 'task1_output_2_value'
on-success:
- my_echo_test_2
my_echo_test_2:
action: std.echo output='just a string'
publish:
task2_output_1: 'task2_output_1_value'
task2_output_2: 'task2_output_2_value'

View File

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

View File

@ -0,0 +1,48 @@
Namespaces:
=: io.murano.apps.test
std: io.murano
res: io.murano.resources
sys: io.murano.system
Name: PolicyEnforcementTestApp
Extends: std:Application
Properties:
name:
Contract: $.string().notNull()
instance:
Contract: $.class(res:Instance).notNull()
host:
Contract: $.string()
Usage: Out
user:
Contract: $.string()
Usage: Out
Methods:
initialize:
Body:
- $._environment: $.find(std:Environment).require()
deploy:
Body:
- If: not $.getAttr(deployed, false)
Then:
- $._environment.reporter.report($this, 'Creating VM')
- $securityGroupIngress:
- ToPort: 22
FromPort: 22
IpProtocol: tcp
External: true
- $._environment.securityGroupManager.addGroupIngress($securityGroupIngress)
- $.instance.deploy()
- $resources: new(sys:Resources)
- $._environment.reporter.report($this, 'Test VM is installed')
- $.host: $.instance.ipAddresses[0]
- $.user: 'root'
- $.setAttr(deployed, true)

View File

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

View File

@ -0,0 +1,18 @@
action("deleteEnv")
murano:states-(eid,st) :- deleteEnv(eid), murano:states( eid, st)
murano:parent_types-(tid, type) :- deleteEnv(eid), murano:connected(eid, tid),murano:parent_types(tid,type)
murano:parent_types-(eid, type) :- deleteEnv(eid), murano:parent_types(eid,type)
murano:properties-(oid, pn, pv) :- deleteEnv(eid), murano:connected( eid, oid),murano:properties(oid, pn, pv)
murano:properties-(eid, pn, pv) :- deleteEnv(eid), murano:properties(eid, pn, pv)
murano:objects-(oid, pid, ot) :- deleteEnv(eid), murano:connected(eid, oid), murano:objects(oid, pid, ot)
murano:objects-(eid, tnid, ot) :- deleteEnv(eid), murano:objects(eid, tnid, ot)
murano:relationships-(sid,tid, rt) :- deleteEnv(eid), murano:connected( eid, sid), murano:relationships( sid, tid, rt)
murano:relationships-(eid,tid, rt) :- deleteEnv(eid), murano:relationships( eid, tid, rt)
murano:connected-(tid, tid2) :- deleteEnv(eid), murano:connected(eid, tid), murano:connected(tid,tid2)
murano:connected-(eid,tid) :- deleteEnv(eid), murano:connected(eid,tid)

View File

@ -0,0 +1,7 @@
missing_key("")
invalid_flavor_name("really.bad.flavor")
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)
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)
murano_env_of_object(oid,eid):-murano:connected(eid,oid), murano:objects(eid,tid,"io.murano.Environment")
bad_flavor_synonyms("horrible.flavor")
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)

View File

@ -0,0 +1,62 @@
# Copyright (c) 2015 OpenStack Foundation
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import uuid
from nose.plugins.attrib import attr as tag
import murano_tempest_tests.tests.functional.common.utils as common_utils
import murano_tempest_tests.tests.functional.integration.integration_base \
as core
class MistralTest(core.MistralIntegration):
@classmethod
def setUpClass(cls):
super(MistralTest, cls).setUpClass()
try:
# Upload the Murano test package.
cls.upload_mistral_showcase_app()
except Exception:
cls.tearDownClass()
raise
@classmethod
def tearDownClass(cls):
with common_utils.ignored(Exception):
cls.purge_environments()
with common_utils.ignored(Exception):
cls.purge_uploaded_packages()
@tag('all', 'coverage')
def test_deploy_package_success(self):
# Test expects successful deployment and one output: input_1_value.
# Create env json string.
post_body = self._create_env_body()
environment_name = 'Mistral_environment' + uuid.uuid4().hex[:5]
# Deploy the environment.
env = self.deploy_apps(environment_name, post_body)
status = self.wait_for_final_status(env)
self.assertIn("ready", status[0],
"Unexpected status : " + status[0])
self.assertIn("input_1_value", status[1],
"Unexpected output value: " + status[1])

View File

@ -0,0 +1,89 @@
# Copyright (c) 2015 OpenStack Foundation
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import muranoclient.common.exceptions as murano_exceptions
from nose.plugins.attrib import attr as tag
import murano_tempest_tests.tests.functional.common.utils as common_utils
import murano_tempest_tests.tests.functional.integration.integration_base \
as core
class PolicyEnforcementTest(core.CongressIntegration):
@classmethod
def setUpClass(cls):
super(PolicyEnforcementTest, cls).setUpClass()
cls._create_policy(["murano", "murano_system"])
cls._create_policy(["murano_action"], kind="action")
with common_utils.ignored(murano_exceptions.HTTPInternalServerError):
cls._upload_policy_enf_app()
@classmethod
def tearDownClass(cls):
cls.purge_uploaded_packages()
def tearDown(self):
super(PolicyEnforcementTest, self).tearDown()
self.purge_environments()
@tag('all', 'coverage')
def test_deploy_policy_fail_key(self):
"""Test expects failure due to empty key name.
In rules_murano_system.txt file are defined congress
rules preventing deploy environment where instances
have empty keyname property. In other words admin
prevented spawn instance without assigned key pair.
"""
self._check_deploy_failure(
self._create_test_app(key='',
flavor='m1.small'),
'missing key')
@tag('all', 'coverage')
def test_deploy_policy_fail_flavor(self):
"""Test expects failure due to blacklisted flavor
In rules_murano_system.txt file are defined congress
rules preventing deploy environment where instances
have flavor property set to 'really.bad.flavor'.
"""
self._check_deploy_failure(
self._create_test_app(flavor='really.bad.flavor',
key='test-key'),
'bad flavor')
@tag('all', 'coverage')
def test_set_property_policy(self):
"""Tests environment modification by policy
In rules_murano_system.txt file are defined congress
rules changing flavor property. There are defined
synonyms for 'really.bad.flavor'. One of such synonyms
is 'horrible.flavor' Environment is modified prior deployment.
The synonym name 'horrible.flavor' is set to original
value 'really.bad.flavor' and then deployment is aborted
because instances of 'really.bad.flavor' are prevented
to be deployed like for the test above.
"""
self._check_deploy_failure(
self._create_test_app(key="test-key",
flavor="horrible.flavor"),
"bad flavor")

View File

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