Allow users to assign a security group to an app
This patch allows users to supply a list* of their own security groups to an instance, rather than using the application defined one (built via the SecurityGroupManager). * Note, while we can support multiple security groups, murano-dashboard currently has no UI element to select multiple items. This means that currently users are restricted to selecting one group. If/when the UI is improved this change can easily support multiple groups. Example ======= Application authors can make this available in their apps as follows: UI.yaml ------- Forms: - instanceConfiguration: fields: ... - name: securityGroups type: securitygroup label: Security Group required: false Class.yaml: ---------- Application: ?: type: com.paul.HelloWorld instance: ?: type: io.murano.resources.LinuxMuranoInstance name: $.instanceConfiguration.hostname securityGroups: $.instanceConfiguration.securityGroups ... DocImpact Change-Id: I60d37cfe034c467e894ee93cf3718e463bf49337 Partially-Implements: blueprint app-use-existing-security-group
This commit is contained in:
parent
580b09d1d4
commit
103f67815a
@ -362,6 +362,8 @@ Currently supported options for **type** attribute are:
|
|||||||
a list
|
a list
|
||||||
* *network* - specific field, used to select a network and subnet from a list
|
* *network* - specific field, used to select a network and subnet from a list
|
||||||
of the ones available to the current user
|
of the ones available to the current user
|
||||||
|
* *securitygroup* - specific field, used for selecting a custom security group
|
||||||
|
to assign to the instance
|
||||||
* any other value is considered to be a fully qualified name for some
|
* any other value is considered to be a fully qualified name for some
|
||||||
Application package and is rendered as a pair of controls: one for selecting
|
Application package and is rendered as a pair of controls: one for selecting
|
||||||
already existing Applications of that type in an Environment, second - for
|
already existing Applications of that type in an Environment, second - for
|
||||||
|
@ -131,6 +131,8 @@ Workflow:
|
|||||||
Contract: $.class(Instance).notNull()
|
Contract: $.class(Instance).notNull()
|
||||||
- securityGroupName:
|
- securityGroupName:
|
||||||
Contract: $.string()
|
Contract: $.string()
|
||||||
|
- securityGroups:
|
||||||
|
Contract: [$.string()]
|
||||||
- assignFloatingIp:
|
- assignFloatingIp:
|
||||||
Contract: $.bool().notNull()
|
Contract: $.bool().notNull()
|
||||||
- sharedIps:
|
- sharedIps:
|
||||||
|
@ -64,6 +64,9 @@ Properties:
|
|||||||
securityGroupName:
|
securityGroupName:
|
||||||
Contract: $.string()
|
Contract: $.string()
|
||||||
Default: null
|
Default: null
|
||||||
|
securityGroups:
|
||||||
|
Contract: [$.string()]
|
||||||
|
Default: []
|
||||||
sharedIps:
|
sharedIps:
|
||||||
Contract:
|
Contract:
|
||||||
- $.class(std:SharedIp)
|
- $.class(std:SharedIp)
|
||||||
@ -135,11 +138,11 @@ Methods:
|
|||||||
- $.ensureNetworksDeployed()
|
- $.ensureNetworksDeployed()
|
||||||
- If: $.networks.useEnvironmentNetwork and $region.defaultNetworks.environment!=null
|
- If: $.networks.useEnvironmentNetwork and $region.defaultNetworks.environment!=null
|
||||||
Then:
|
Then:
|
||||||
$.joinNet($region.defaultNetworks.environment, $securityGroupName)
|
$.joinNet($region.defaultNetworks.environment, $securityGroupName, $this.securityGroups)
|
||||||
- If: $.networks.useFlatNetwork and $region.defaultNetworks.flat!=null
|
- If: $.networks.useFlatNetwork and $region.defaultNetworks.flat!=null
|
||||||
Then:
|
Then:
|
||||||
$.joinNet($region.defaultNetworks.flat, $securityGroupName)
|
$.joinNet($region.defaultNetworks.flat, $securityGroupName, $this.securityGroups)
|
||||||
- $.networks.customNetworks.select($this.joinNet($, $securityGroupName))
|
- $.networks.customNetworks.select($this.joinNet($, $securityGroupName, $this.securityGroups))
|
||||||
|
|
||||||
- $preparedUserData: $.prepareUserData()
|
- $preparedUserData: $.prepareUserData()
|
||||||
- $properties:
|
- $properties:
|
||||||
@ -251,6 +254,8 @@ Methods:
|
|||||||
Contract: $.class(Network).notNull()
|
Contract: $.class(Network).notNull()
|
||||||
- securityGroupName:
|
- securityGroupName:
|
||||||
Contract: $.string()
|
Contract: $.string()
|
||||||
|
- securityGroups:
|
||||||
|
Contract: [$.string()]
|
||||||
Body:
|
Body:
|
||||||
- $primary: $net = $._primaryNetwork
|
- $primary: $net = $._primaryNetwork
|
||||||
- $assignFip: $primary and $.assignFloatingIp and not $.getAttr(fipAssigned, false)
|
- $assignFip: $primary and $.assignFloatingIp and not $.getAttr(fipAssigned, false)
|
||||||
@ -261,6 +266,7 @@ Methods:
|
|||||||
- $joinResult: $net.joinInstance(
|
- $joinResult: $net.joinInstance(
|
||||||
instance => $this,
|
instance => $this,
|
||||||
securityGroupName => $securityGroupName,
|
securityGroupName => $securityGroupName,
|
||||||
|
securityGroups => $securityGroups,
|
||||||
assignFloatingIp => $assignFip,
|
assignFloatingIp => $assignFip,
|
||||||
sharedIps => $sharedIps
|
sharedIps => $sharedIps
|
||||||
)
|
)
|
||||||
|
@ -127,6 +127,8 @@ Methods:
|
|||||||
Contract: $.class(Instance).notNull()
|
Contract: $.class(Instance).notNull()
|
||||||
- securityGroupName:
|
- securityGroupName:
|
||||||
Contract: $.string()
|
Contract: $.string()
|
||||||
|
- securityGroups:
|
||||||
|
Contract: [$.string()]
|
||||||
- assignFloatingIp:
|
- assignFloatingIp:
|
||||||
Contract: $.bool().notNull()
|
Contract: $.bool().notNull()
|
||||||
- sharedIps:
|
- sharedIps:
|
||||||
@ -146,6 +148,7 @@ Methods:
|
|||||||
- $result: $.joinInstanceToNetwork(
|
- $result: $.joinInstanceToNetwork(
|
||||||
instance => $instance,
|
instance => $instance,
|
||||||
securityGroupName => $securityGroupName,
|
securityGroupName => $securityGroupName,
|
||||||
|
securityGroups => $securityGroups,
|
||||||
sharedIps => $sharedIps,
|
sharedIps => $sharedIps,
|
||||||
netRef => $netRef,
|
netRef => $netRef,
|
||||||
subnetRef => $subnetRef,
|
subnetRef => $subnetRef,
|
||||||
|
@ -38,6 +38,8 @@ Methods:
|
|||||||
Contract: $.class(Instance).notNull()
|
Contract: $.class(Instance).notNull()
|
||||||
- securityGroupName:
|
- securityGroupName:
|
||||||
Contract: $.string()
|
Contract: $.string()
|
||||||
|
- securityGroups:
|
||||||
|
Contract: [$.string()]
|
||||||
- sharedIps:
|
- sharedIps:
|
||||||
Contract:
|
Contract:
|
||||||
- $.class(std:SharedIp)
|
- $.class(std:SharedIp)
|
||||||
@ -80,15 +82,30 @@ Methods:
|
|||||||
- subnet: $subnetRef
|
- subnet: $subnetRef
|
||||||
- $patchTemplate: $patchTemplate.mergeWith($template)
|
- $patchTemplate: $patchTemplate.mergeWith($template)
|
||||||
|
|
||||||
- If: bool($securityGroupName) and $securityGroupsEnabled
|
- If: $securityGroupsEnabled
|
||||||
Then:
|
Then:
|
||||||
- $template:
|
- If: len($securityGroups) > 0 and $securityGroups[0] != ""
|
||||||
resources:
|
Then:
|
||||||
$portName:
|
- For: securityGroup
|
||||||
properties:
|
In: $securityGroups
|
||||||
security_groups:
|
Do:
|
||||||
- get_resource: $securityGroupName
|
- $template:
|
||||||
- $patchTemplate: $patchTemplate.mergeWith($template)
|
resources:
|
||||||
|
$portName:
|
||||||
|
properties:
|
||||||
|
security_groups:
|
||||||
|
- $securityGroup
|
||||||
|
- $patchTemplate: $patchTemplate.mergeWith($template)
|
||||||
|
Else:
|
||||||
|
- If: bool($securityGroupName)
|
||||||
|
Then:
|
||||||
|
- $template:
|
||||||
|
resources:
|
||||||
|
$portName:
|
||||||
|
properties:
|
||||||
|
security_groups:
|
||||||
|
- get_resource: $securityGroupName
|
||||||
|
- $patchTemplate: $patchTemplate.mergeWith($template)
|
||||||
|
|
||||||
- $instanceResources: [$portName]
|
- $instanceResources: [$portName]
|
||||||
- $instanceOutputs: [$addressesOutputName]
|
- $instanceOutputs: [$addressesOutputName]
|
||||||
|
@ -212,6 +212,32 @@ class BaseApplicationCatalogScenarioTest(base.BaseTestCase):
|
|||||||
}
|
}
|
||||||
return post_body
|
return post_body
|
||||||
|
|
||||||
|
def vm_test(self, **kwargs):
|
||||||
|
instance = {
|
||||||
|
"flavor": "m1.small",
|
||||||
|
"image": self.cirros_image,
|
||||||
|
"assignFloatingIp": True,
|
||||||
|
"availabilityZone": "nova",
|
||||||
|
"?": {
|
||||||
|
"type": "io.murano.resources.LinuxMuranoInstance",
|
||||||
|
"id": utils.generate_uuid()
|
||||||
|
},
|
||||||
|
"name": utils.generate_name("testMurano")
|
||||||
|
}
|
||||||
|
if kwargs.get('securityGroups'):
|
||||||
|
instance['securityGroups'] = kwargs.get('securityGroups')
|
||||||
|
return {
|
||||||
|
"instance": instance,
|
||||||
|
"name": utils.generate_name("VM"),
|
||||||
|
"?": {
|
||||||
|
"_{id}".format(id=utils.generate_uuid()): {
|
||||||
|
"name": "VM"
|
||||||
|
},
|
||||||
|
"type": "io.murano.apps.test.VM",
|
||||||
|
"id": utils.generate_uuid()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
def update_executor(self, flavor='m1.small'):
|
def update_executor(self, flavor='m1.small'):
|
||||||
post_body = {
|
post_body = {
|
||||||
"instance": {
|
"instance": {
|
||||||
@ -365,7 +391,7 @@ class BaseApplicationCatalogScenarioTest(base.BaseTestCase):
|
|||||||
instance_id = self.get_instance_id(name)
|
instance_id = self.get_instance_id(name)
|
||||||
attached_volumes = self.servers_client.\
|
attached_volumes = self.servers_client.\
|
||||||
list_volume_attachments(instance_id)['volumeAttachments']
|
list_volume_attachments(instance_id)['volumeAttachments']
|
||||||
assert attached_volumes[0]['id'] == volume_id
|
self.assertEqual(attached_volumes[0]['id'], volume_id)
|
||||||
|
|
||||||
|
|
||||||
class BaseApplicationCatalogScenarioIsolatedAdminTest(
|
class BaseApplicationCatalogScenarioIsolatedAdminTest(
|
||||||
|
@ -0,0 +1,82 @@
|
|||||||
|
#
|
||||||
|
# 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 testtools
|
||||||
|
|
||||||
|
from tempest import config
|
||||||
|
|
||||||
|
from murano_tempest_tests.tests.scenario.application_catalog import base
|
||||||
|
from murano_tempest_tests import utils
|
||||||
|
|
||||||
|
CONF = config.CONF
|
||||||
|
|
||||||
|
|
||||||
|
class TestSecurityGroups(base.BaseApplicationCatalogScenarioTest):
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def resource_setup(cls):
|
||||||
|
super(TestSecurityGroups, cls).resource_setup()
|
||||||
|
cls.linux = CONF.application_catalog.linux_image
|
||||||
|
application_name = utils.generate_name('VM')
|
||||||
|
cls.abs_archive_path, dir_with_archive, archive_name = \
|
||||||
|
utils.prepare_package(
|
||||||
|
application_name,
|
||||||
|
app='io.murano.apps.test.VM',
|
||||||
|
manifest_required=False)
|
||||||
|
if CONF.application_catalog.glare_backend:
|
||||||
|
cls.client = cls.artifacts_client
|
||||||
|
else:
|
||||||
|
cls.client = cls.application_catalog_client
|
||||||
|
cls.package = cls.client.upload_package(
|
||||||
|
application_name, archive_name, dir_with_archive,
|
||||||
|
{"categories": ["Web"], "tags": ["test"]})
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def resource_cleanup(cls):
|
||||||
|
cls.client.delete_package(cls.package['id'])
|
||||||
|
os.remove(cls.abs_archive_path)
|
||||||
|
super(TestSecurityGroups, cls).resource_cleanup()
|
||||||
|
|
||||||
|
@testtools.testcase.attr('smoke')
|
||||||
|
@testtools.testcase.attr('scenario')
|
||||||
|
def test_deploy_app_with_murano_defined_security_group(self):
|
||||||
|
name = utils.generate_name('testMurano')
|
||||||
|
environment = self.application_catalog_client.create_environment(name)
|
||||||
|
session = self.application_catalog_client.create_session(
|
||||||
|
environment['id'])
|
||||||
|
self.application_catalog_client.create_service(
|
||||||
|
environment['id'], session['id'], self.vm_test())
|
||||||
|
self.deploy_environment(environment, session)
|
||||||
|
instance_id = self.get_instance_id('testMurano')
|
||||||
|
security_groups = self.servers_client.list_security_groups_by_server(
|
||||||
|
instance_id).get('security_groups')
|
||||||
|
self.assertEqual(len(security_groups), 1)
|
||||||
|
self.assertEqual(len(security_groups[0].get('rules')), 4)
|
||||||
|
|
||||||
|
@testtools.testcase.attr('smoke')
|
||||||
|
@testtools.testcase.attr('scenario')
|
||||||
|
def test_deploy_app_with_user_defined_security_group(self):
|
||||||
|
name = utils.generate_name('testMurano')
|
||||||
|
environment = self.application_catalog_client.create_environment(name)
|
||||||
|
session = self.application_catalog_client.create_session(
|
||||||
|
environment['id'])
|
||||||
|
self.application_catalog_client.create_service(
|
||||||
|
environment['id'], session['id'],
|
||||||
|
self.vm_test(securityGroups=['default']))
|
||||||
|
self.deploy_environment(environment, session)
|
||||||
|
instance_id = self.get_instance_id('testMurano')
|
||||||
|
security_groups = self.servers_client.list_security_groups_by_server(
|
||||||
|
instance_id).get('security_groups')
|
||||||
|
self.assertEqual(len(security_groups), 1)
|
||||||
|
self.assertEqual('default', security_groups[0].get('name'))
|
@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
features:
|
||||||
|
- |
|
||||||
|
Users can now assign an existing security group to an application as
|
||||||
|
an alternative to using the one created by Murano's ``SecurityGroupManager``.
|
Loading…
Reference in New Issue
Block a user