Merge "Nova Network support"

This commit is contained in:
Jenkins 2015-04-08 14:05:17 +00:00 committed by Gerrit Code Review
commit 5e909c98e3
17 changed files with 273 additions and 61 deletions

View File

@ -17,17 +17,38 @@
Network Configuration Network Configuration
--------------------- ---------------------
To work with Murano, tenant network in Openstack installation should be configured in a certain way. Murano may work in various networking environments and is capable to detect the
This configuration may be set up automatically with the provision of several parameters in config file or manually. current network configuration and choose the appropriate settings automatically.
However, some additional actions are required to support advanced scenarios.
Murano has advanced networking features that give you ability to not care about configuring networks
for your application. By default it will create an isolated network for each environment and join Nova network support
^^^^^^^^^^^^^^^^^^^^
Nova Network is simplest networking solution, which has limited capabilities
but is available on any OpenStack deployment without the need to deploy any
additional components.
When a new Murano Environment is created, Murano checks if a dedicated
networking service (i.e. Neutron) exists in the current OpenStack deployment.
It relies on Keystone's service catalog for that.
If such a service is not present, Murano automatically falls back to Nova
Network. No further configuration is needed in this case, all the VMs spawned
by Murano will be joining the same Network.
Neutron support
^^^^^^^^^^^^^^^
If Neutron is installed, Murano enables its advanced networking features that
give you ability to not care about configuring networks for your application.
By default it will create an isolated network for each environment and join
all VMs needed by your application to that network. To install and configure application in all VMs needed by your application to that network. To install and configure application in
just spawned virtual machine Murano also requires a router connected to the external network. just spawned virtual machine Murano also requires a router connected to the external network.
Automatic network configuration Automatic Neutron network configuration
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
To create router automatically, provide the following parameters in config file: To create router automatically, provide the following parameters in config file:
@ -49,8 +70,8 @@ To figure out the name of the external network, perform the following command:
During the first deploy, required networks and router with specified name will be created and set up. During the first deploy, required networks and router with specified name will be created and set up.
Manual network configuration Manual neutron network configuration
^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
* Step 1. Create public network * Step 1. Create public network

View File

@ -47,14 +47,22 @@ Methods:
- $generatedEnvironmentName: randomName() - $generatedEnvironmentName: randomName()
- $.setAttr(generatedEnvironmentName, $generatedEnvironmentName) - $.setAttr(generatedEnvironmentName, $generatedEnvironmentName)
- $this.agentListener: new(sys:AgentListener, name => $generatedEnvironmentName) - $this.agentListener: new(sys:AgentListener, name => $generatedEnvironmentName)
- $stackDescriptionFormat: 'This stack was generated by Murano for environment {0} (ID: {1})' - $stackDescriptionFormat: 'This stack was generated by Murano for environment {0} (ID: {1})'
- $this.stack: new(sys:HeatStack, - $this.stack: new(sys:HeatStack,
name => 'murano-' + $generatedEnvironmentName, name => 'murano-' + $generatedEnvironmentName,
description => $stackDescriptionFormat.format($.name, $.id())) description => $stackDescriptionFormat.format($.name, $.id()))
- $this.instanceNotifier: new(sys:InstanceNotifier, environment => $this) - $this.instanceNotifier: new(sys:InstanceNotifier, environment => $this)
- $this.reporter: new(sys:StatusReporter, environment => $this) - $this.reporter: new(sys:StatusReporter, environment => $this)
- $this.securityGroupManager: new(sys:SecurityGroupManager, environment => $this) - $net: null
- If: $.defaultNetworks.environment != null
Then:
- $net: $.defaultNetworks.environment
- If: $.defaultNetworks.flat != null
Then:
- $net: $.defaultNetworks.flat
- If: $net != null
Then:
- $this.securityGroupManager: $net.generateSecurityGroupManager($this)
deploy: deploy:
Usage: Action Usage: Action

View File

@ -180,8 +180,10 @@ Methods:
assignFloatingIp => $assignFip, assignFloatingIp => $assignFip,
sharedIps => $sharedIps sharedIps => $sharedIps
) )
- $.instanceTemplate: $.instanceTemplate.mergeWith($joinResult.template) - If: $joinResult.template != null
Then:
- $.instanceTemplate: $.instanceTemplate.mergeWith($joinResult.template)
- If: $joinResult.portRef != null - If: $joinResult.portRef != null
Then: Then:
- $template: - $template:
@ -192,6 +194,16 @@ Methods:
- port: - port:
$joinResult.portRef $joinResult.portRef
- $.instanceTemplate: $.instanceTemplate.mergeWith($template) - $.instanceTemplate: $.instanceTemplate.mergeWith($template)
- If: $joinResult.secGroupName != null
Then:
- $template:
resources:
$.name:
properties:
security_groups:
- $joinResult.secGroupName
- $.instanceTemplate: $.instanceTemplate.mergeWith($template)
- $._floatingIpOutputName: coalesce($._floatingIpOutputName, $joinResult.instanceFipOutput) - $._floatingIpOutputName: coalesce($._floatingIpOutputName, $joinResult.instanceFipOutput)
- If: $assignFip and $joinResult.instanceFipOutput != null - If: $assignFip and $joinResult.instanceFipOutput != null

View File

@ -18,3 +18,8 @@ Methods:
- sharedIps: - sharedIps:
Contract: Contract:
- $.class(std:SharedIp) - $.class(std:SharedIp)
generateSecurityGroupManager:
Arguments:
- environment:
Contract: $.class(std:Environment).notNull()

View File

@ -1,6 +1,7 @@
Namespaces: Namespaces:
=: io.murano.resources =: io.murano.resources
std: io.murano std: io.murano
sys: io.murano.system
Name: NeutronNetworkBase Name: NeutronNetworkBase
@ -80,3 +81,11 @@ Methods:
portRef: portRef:
get_resource: $portName get_resource: $portName
instanceFipOutput: $instanceFipOutput instanceFipOutput: $instanceFipOutput
generateSecurityGroupManager:
Arguments:
- environment:
Contract: $.class(std:Environment).notNull()
Body:
- Return: new(sys:NeutronSecurityGroupManager, environment => $environment)

View File

@ -0,0 +1,57 @@
Namespaces:
=: io.murano.resources
std: io.murano
sys: io.murano.system
Name: NovaNetwork
Extends: Network
Methods:
joinInstance:
Arguments:
- instance:
Contract: $.class(Instance).notNull()
- securityGroupName:
Contract: $.string()
- assignFloatingIp:
Contract: $.bool().notNull()
- sharedIps:
Contract:
- $.class(std:SharedIp)
Body:
- $fipName: null
- $template: null
- $instanceFipOutput: null
- If: $assignFloatingIp
Then:
- $instanceFipOutput: $instance.name + '-floatingIPaddress'
- $fipName: format('fip-nn-{0}', $instance.name)
- $template:
resources:
$fipName:
type: 'OS::Nova::FloatingIP'
$fipName + 'Assignment':
type: 'OS::Nova::FloatingIPAssociation'
properties:
floating_ip:
get_resource: $fipName
server_id:
get_resource: $instance.name
outputs:
$instanceFipOutput:
value:
get_attr: [$fipName, ip]
description: format('Floating IP of {0}', $instance.name)
- Return:
template: $template
secGroupName:
get_resource: $securityGroupName
instanceFipOutput: instanceFipOutput
generateSecurityGroupManager:
Arguments:
- environment:
Contract: $.class(std:Environment).notNull()
Body:
- Return: new(sys:AwsSecurityGroupManager, environment => $environment)

View File

@ -0,0 +1,57 @@
Namespaces:
=: io.murano.system
std: io.murano
Name: AwsSecurityGroupManager
Extends: SecurityGroupManager
Methods:
addGroupIngress:
Arguments:
- rules:
Contract:
- FromPort: $.int().notNull()
ToPort: $.int().notNull()
IpProtocol: $.string().notNull()
External: $.bool().notNull()
- groupName:
Contract: $.string().notNull()
Default: $this.defaultGroupName
Body:
- $ext_keys:
true:
ext_key: remote_ip_prefix
ext_val: '0.0.0.0/0'
false:
ext_key: remote_mode
ext_val: remote_group_id
- $stack: $.environment.stack
- $template:
resources:
$groupName:
type: 'AWS::EC2::SecurityGroup'
properties:
GroupDescription: format('Composite security group of Murano environment {0}', $.environment.name)
SecurityGroupIngress:
- FromPort: '-1'
ToPort: '-1'
IpProtocol: icmp
CidrIp: '0.0.0.0/0'
- $.environment.stack.updateTemplate($template)
- $ingress: $rules.select(dict(
FromPort => str($.FromPort),
ToPort => str($.ToPort),
IpProtocol => $.IpProtocol,
CidrIp => '0.0.0.0/0'
))
- $template:
resources:
$groupName:
type: 'AWS::EC2::SecurityGroup'
properties:
SecurityGroupIngress: $ingress
- $.environment.stack.updateTemplate($template)

View File

@ -1,16 +1,10 @@
Namespaces: Namespaces:
=: io.murano.system =: io.murano.system
std: io.murano std: io.murano
Name: SecurityGroupManager Name: NeutronSecurityGroupManager
Properties: Extends: SecurityGroupManager
environment:
Contract: $.class(std:Environment).notNull()
defaultGroupName:
Contract: $.string()
Default: format('MuranoSecurityGroup-{0}', $.environment.name)
Methods: Methods:
addGroupIngress: addGroupIngress:
@ -61,8 +55,3 @@ Methods:
properties: properties:
rules: $ingress rules: $ingress
- $.environment.stack.updateTemplate($template) - $.environment.stack.updateTemplate($template)

View File

@ -0,0 +1,26 @@
Namespaces:
=: io.murano.system
std: io.murano
Name: SecurityGroupManager
Properties:
environment:
Contract: $.class(std:Environment).notNull()
defaultGroupName:
Contract: $.string()
Default: format('MuranoSecurityGroup-{0}', $.environment.name)
Methods:
addGroupIngress:
Arguments:
- rules:
Contract:
- FromPort: $.int().notNull()
ToPort: $.int().notNull()
IpProtocol: $.string().notNull()
External: $.bool().notNull()
- groupName:
Contract: $.string().notNull()
Default: $this.defaultGroupName

View File

@ -22,7 +22,6 @@ Classes:
io.murano.SharedIp: SharedIp.yaml io.murano.SharedIp: SharedIp.yaml
io.murano.File: File.yaml io.murano.File: File.yaml
io.murano.system.SecurityGroupManager: SecurityGroupManager.yaml
io.murano.resources.Network: resources/Network.yaml io.murano.resources.Network: resources/Network.yaml
io.murano.resources.Instance: resources/Instance.yaml io.murano.resources.Instance: resources/Instance.yaml
@ -35,6 +34,7 @@ Classes:
io.murano.resources.NeutronNetworkBase: resources/NeutronNetworkBase.yaml io.murano.resources.NeutronNetworkBase: resources/NeutronNetworkBase.yaml
io.murano.resources.NeutronNetwork: resources/NeutronNetwork.yaml io.murano.resources.NeutronNetwork: resources/NeutronNetwork.yaml
io.murano.resources.ExistingNeutronNetwork: resources/ExistingNeutronNetwork.yaml io.murano.resources.ExistingNeutronNetwork: resources/ExistingNeutronNetwork.yaml
io.murano.resources.NovaNetwork: resources/NovaNetwork.yaml
io.murano.system.Agent: system/Agent.yaml io.murano.system.Agent: system/Agent.yaml
io.murano.system.AgentListener: system/AgentListener.yaml io.murano.system.AgentListener: system/AgentListener.yaml
@ -43,3 +43,6 @@ Classes:
io.murano.system.InstanceNotifier: system/InstanceNotifier.yaml io.murano.system.InstanceNotifier: system/InstanceNotifier.yaml
io.murano.system.StatusReporter: system/StatusReporter.yaml io.murano.system.StatusReporter: system/StatusReporter.yaml
io.murano.system.NetworkExplorer: system/NetworkExplorer.yaml io.murano.system.NetworkExplorer: system/NetworkExplorer.yaml
io.murano.system.SecurityGroupManager: system/SecurityGroupManager.yaml
io.murano.system.NeutronSecurityGroupManager: system/NeutronSecurityGroupManager.yaml
io.murano.system.AwsSecurityGroupManager: system/AwsSecurityGroupManager.yaml

View File

@ -59,7 +59,7 @@ class Controller(object):
try: try:
environment = envs.EnvironmentServices.create( environment = envs.EnvironmentServices.create(
body.copy(), body.copy(),
request.context.tenant) request.context)
except db_exc.DBDuplicateEntry: except db_exc.DBDuplicateEntry:
msg = _('Environment with specified name already exists') msg = _('Environment with specified name already exists')
LOG.exception(msg) LOG.exception(msg)

View File

@ -183,7 +183,7 @@ class Controller(object):
try: try:
environment = envs.EnvironmentServices.create( environment = envs.EnvironmentServices.create(
body.copy(), request.context.tenant) body.copy(), request.context)
except db_exc.DBDuplicateEntry: except db_exc.DBDuplicateEntry:
msg = _('Environment with specified name already exists') msg = _('Environment with specified name already exists')
LOG.exception(msg) LOG.exception(msg)

View File

@ -18,11 +18,11 @@ from oslo.config import cfg
from oslo.utils import importutils from oslo.utils import importutils
def get_client(environment): def get_client(token, tenant_id):
settings = _get_keystone_settings() settings = _get_keystone_settings()
kwargs = { kwargs = {
'token': environment.token, 'token': token,
'tenant_id': environment.tenant_id, 'tenant_id': tenant_id,
'auth_url': settings['auth_url'] 'auth_url': settings['auth_url']
} }
kwargs.update(settings['ssl']) kwargs.update(settings['ssl'])
@ -58,12 +58,12 @@ def _admin_client(trust_id=None, project_name=None):
return client return client
def get_client_for_trusts(environment): def get_client_for_trusts(trust_id):
return _admin_client(environment.trust_id) return _admin_client(trust_id)
def create_trust(environment): def create_trust(token, tenant_id):
client = get_client(environment) client = get_client(token, tenant_id)
settings = _get_keystone_settings() settings = _get_keystone_settings()
trustee_id = get_client_for_admin( trustee_id = get_client_for_admin(
@ -74,14 +74,14 @@ def create_trust(environment):
trustee_user=trustee_id, trustee_user=trustee_id,
impersonation=True, impersonation=True,
role_names=roles, role_names=roles,
project=environment.tenant_id) project=tenant_id)
return trust.id return trust.id
def delete_trust(environment): def delete_trust(trust_id):
keystone_client = get_client_for_trusts(environment) keystone_client = get_client_for_trusts(trust_id)
keystone_client.trusts.delete(environment.trust_id) keystone_client.trusts.delete(trust_id)
def _get_keystone_settings(): def _get_keystone_settings():

View File

@ -21,6 +21,7 @@ from oslo import messaging
from oslo.messaging import target from oslo.messaging import target
from oslo.serialization import jsonutils from oslo.serialization import jsonutils
from murano.common import auth_utils
from murano.common import config from murano.common import config
from murano.common.helpers import token_sanitizer from murano.common.helpers import token_sanitizer
from murano.common import plugin_loader from murano.common import plugin_loader
@ -28,7 +29,6 @@ from murano.common import rpc
from murano.dsl import dsl_exception from murano.dsl import dsl_exception
from murano.dsl import executor from murano.dsl import executor
from murano.dsl import serializer from murano.dsl import serializer
from murano.engine import auth_utils
from murano.engine import client_manager from murano.engine import client_manager
from murano.engine import environment from murano.engine import environment
from murano.engine import package_class_loader from murano.engine import package_class_loader
@ -233,13 +233,14 @@ class TaskExecutor(object):
return return
trust_id = self._environment.system_attributes.get('TrustId') trust_id = self._environment.system_attributes.get('TrustId')
if not trust_id: if not trust_id:
trust_id = auth_utils.create_trust(self._environment) trust_id = auth_utils.create_trust(self._environment.token,
self._environment.tenant_id)
self._environment.system_attributes['TrustId'] = trust_id self._environment.system_attributes['TrustId'] = trust_id
self._environment.trust_id = trust_id self._environment.trust_id = trust_id
def _delete_trust(self): def _delete_trust(self):
trust_id = self._environment.trust_id trust_id = self._environment.trust_id
if trust_id: if trust_id:
auth_utils.delete_trust(self._environment) auth_utils.delete_trust(self._environment.trust_id)
self._environment.system_attributes['TrustId'] = None self._environment.system_attributes['TrustId'] = None
self._environment.trust_id = None self._environment.trust_id = None

View File

@ -14,15 +14,23 @@
import yaml import yaml
from keystoneclient import exceptions as ks_exceptions
from murano.common import auth_utils
from murano.common import config from murano.common import config
from murano.common import uuidutils from murano.common import uuidutils
from murano.db import models from murano.db import models
from murano.db.services import sessions from murano.db.services import sessions
from murano.db import session as db_session from murano.db import session as db_session
from murano.openstack.common import log as logging
from murano.services import states from murano.services import states
DEFAULT_NETWORK_TYPE = 'io.murano.resources.NeutronNetwork' LOG = logging.getLogger(__name__)
DEFAULT_NETWORK_TYPES = {
"nova": 'io.murano.resources.NovaNetwork',
"neutron": 'io.murano.resources.NeutronNetwork'
}
class EnvironmentServices(object): class EnvironmentServices(object):
@ -78,24 +86,25 @@ class EnvironmentServices(object):
return states.EnvironmentStatus.READY return states.EnvironmentStatus.READY
@staticmethod @staticmethod
def create(environment_params, tenant_id): def create(environment_params, context):
# tagging environment by tenant_id for later checks # tagging environment by tenant_id for later checks
"""Creates environment with specified params, in particular - name """Creates environment with specified params, in particular - name
:param environment_params: Dict, e.g. {'name': 'env-name'} :param environment_params: Dict, e.g. {'name': 'env-name'}
:param tenant_id: Tenant Id :param context: request context to get the tenant id and the token
:return: Created Environment :return: Created Environment
""" """
objects = {'?': { objects = {'?': {
'id': uuidutils.generate_uuid(), 'id': uuidutils.generate_uuid(),
}} }}
network_driver = EnvironmentServices.get_network_driver(context)
objects.update(environment_params) objects.update(environment_params)
if not objects.get('defaultNetworks'): if not objects.get('defaultNetworks'):
objects['defaultNetworks'] = \ objects['defaultNetworks'] = \
EnvironmentServices.generate_default_networks(objects['name']) EnvironmentServices.generate_default_networks(objects['name'],
network_driver)
objects['?']['type'] = 'io.murano.Environment' objects['?']['type'] = 'io.murano.Environment'
environment_params['tenant_id'] = tenant_id environment_params['tenant_id'] = context.tenant
data = { data = {
'Objects': objects, 'Objects': objects,
@ -196,26 +205,25 @@ class EnvironmentServices(object):
session.save(unit) session.save(unit)
@staticmethod @staticmethod
def generate_default_networks(env_name): def generate_default_networks(env_name, network_driver):
net_config = config.CONF.find_file( net_config = config.CONF.find_file(
config.CONF.networking.network_config_file) config.CONF.networking.network_config_file)
if net_config: if net_config:
LOG.debug("Loading network configuration from file")
with open(net_config) as f: with open(net_config) as f:
data = yaml.safe_load(f) data = yaml.safe_load(f)
return EnvironmentServices._objectify(data, { return EnvironmentServices._objectify(data, {
'ENV': env_name 'ENV': env_name
}) })
# TODO(ativelkov): network_type = DEFAULT_NETWORK_TYPES[network_driver]
# This is a temporary workaround. Need to find a better way: LOG.debug("Setting '{0}' as environment's "
# These objects have to be created in runtime when the environment is "default network".format(network_type))
# deployed for the first time. Currently there is no way to persist
# such changes, so we have to create the objects on the API side
return { return {
'environment': { 'environment': {
'?': { '?': {
'id': uuidutils.generate_uuid(), 'id': uuidutils.generate_uuid(),
'type': DEFAULT_NETWORK_TYPE 'type': network_type
}, },
'name': env_name + '-network' 'name': env_name + '-network'
}, },
@ -250,3 +258,15 @@ class EnvironmentServices(object):
for key, value in replacements.iteritems(): for key, value in replacements.iteritems():
data = data.replace('%' + key + '%', value) data = data.replace('%' + key + '%', value)
return data return data
@staticmethod
def get_network_driver(context):
ks = auth_utils.get_client(context.auth_token, context.tenant)
try:
ks.service_catalog.url_for(service_type='network')
except ks_exceptions.EndpointNotFound:
LOG.debug("Will use NovaNetwork as a network driver")
return "nova"
else:
LOG.debug("Will use Neutron as a network driver")
return "neutron"

View File

@ -19,11 +19,12 @@ import muranoclient.v1.client as muranoclient
import neutronclient.v2_0.client as nclient import neutronclient.v2_0.client as nclient
from oslo.config import cfg from oslo.config import cfg
from murano.common import auth_utils
from murano.common import config from murano.common import config
from murano.dsl import helpers from murano.dsl import helpers
from murano.engine import auth_utils
from murano.engine import environment from murano.engine import environment
try: try:
# integration with congress is optional # integration with congress is optional
import congressclient.v1.client as congress_client import congressclient.v1.client as congress_client
@ -77,8 +78,9 @@ class ClientManager(object):
if not config.CONF.engine.use_trusts: if not config.CONF.engine.use_trusts:
use_trusts = False use_trusts = False
env = self._get_environment(context) env = self._get_environment(context)
factory = lambda _1, _2: auth_utils.get_client_for_trusts(env) \ factory = lambda _1, _2: \
if use_trusts else auth_utils.get_client(env) auth_utils.get_client_for_trusts(env.trust_id) \
if use_trusts else auth_utils.get_client(env.token, env.tenant_id)
return self.get_client(context, 'keystone', use_trusts, factory) return self.get_client(context, 'keystone', use_trusts, factory)

View File

@ -80,6 +80,8 @@ class MuranoApiTestCase(base.MuranoWithDBTestCase, FakeLogMixin):
return_value=self.mock_engine_rpc).start() return_value=self.mock_engine_rpc).start()
mock.patch(self.RPC_IMPORT + '.api', mock.patch(self.RPC_IMPORT + '.api',
return_value=self.mock_api_rpc).start() return_value=self.mock_api_rpc).start()
mock.patch('murano.db.services.environments.EnvironmentServices.'
'get_network_driver', return_value='neutron').start()
self.addCleanup(mock.patch.stopall) self.addCleanup(mock.patch.stopall)