Implement multisite VIM feature
Added support for multisite OpenStack VIM feature through a new 'nfvo' extension. This also enables remote VNF life cycle manage- ment through the VIM Id parameter. Implements: blueprint multi-site-vim Depends-On: I3c08945f24343288c2c5614ab4b472d68a1e1d47 Change-Id: I7dd19a0c1ce948474bb3069073b3608ce265beb4
This commit is contained in:
parent
905c4ade56
commit
0fdd5a4717
@ -388,4 +388,18 @@ function tacker_create_initial_network {
|
||||
sudo ifconfig ${BR_MGMT} inet ${NETWORK_GATEWAY_MGMT_IP}
|
||||
}
|
||||
|
||||
|
||||
function tacker_register_default_vim {
|
||||
local default_vim_id
|
||||
DEFAULT_VIM_PROJECT_NAME="nfv"
|
||||
DEFAULT_VIM_USER="nfv_user"
|
||||
DEFAULT_VIM_PASSWORD="devstack"
|
||||
DEFAULT_VIM_NAME="VIM0"
|
||||
get_or_create_project $DEFAULT_VIM_PROJECT_NAME
|
||||
get_or_create_user $DEFAULT_VIM_USER $DEFAULT_VIM_PASSWORD
|
||||
get_or_add_user_project_role "admin" $DEFAULT_VIM_USER $DEFAULT_VIM_PROJECT_NAME
|
||||
get_or_add_user_project_role "advsvc" $DEFAULT_VIM_USER $DEFAULT_VIM_PROJECT_NAME
|
||||
VIM_CONFIG_FILE="$TACKER_DIR/devstack/samples/vim_config.yaml"
|
||||
default_vim_id=$(tacker vim-register --name $DEFAULT_VIM_NAME --config-file $VIM_CONFIG_FILE -f value -c id)
|
||||
echo $default_vim_id
|
||||
iniset $TACKER_CONF nfvo_vim default_vim $DEFAULT_VIM_NAME
|
||||
}
|
||||
|
@ -34,7 +34,8 @@ if is_service_enabled tacker; then
|
||||
tacker_create_initial_network
|
||||
echo_summary "Upload OpenWrt image"
|
||||
tacker_create_openwrt_image
|
||||
|
||||
echo_summary "Registering default VIM"
|
||||
tacker_register_default_vim
|
||||
fi
|
||||
|
||||
if [[ "$1" == "unstack" ]]; then
|
||||
|
4
devstack/samples/vim_config.yaml
Normal file
4
devstack/samples/vim_config.yaml
Normal file
@ -0,0 +1,4 @@
|
||||
auth_url: 'http://localhost:5000'
|
||||
username: 'nfv_user'
|
||||
password: 'devstack'
|
||||
project_name: 'nfv'
|
@ -1,136 +1,10 @@
|
||||
{
|
||||
"context_is_admin": "role:admin",
|
||||
"admin_or_owner": "rule:context_is_admin or tenant_id:%(tenant_id)s",
|
||||
"admin_or_network_owner": "rule:context_is_admin or tenant_id:%(network:tenant_id)s",
|
||||
"admin_only": "rule:context_is_admin",
|
||||
"regular_user": "",
|
||||
"shared": "field:networks:shared=True",
|
||||
"shared_firewalls": "field:firewalls:shared=True",
|
||||
"external": "field:networks:router:external=True",
|
||||
"shared": "field:vims:shared=True",
|
||||
"default": "rule:admin_or_owner",
|
||||
|
||||
"subnets:private:read": "rule:admin_or_owner",
|
||||
"subnets:private:write": "rule:admin_or_owner",
|
||||
"subnets:shared:read": "rule:regular_user",
|
||||
"subnets:shared:write": "rule:admin_only",
|
||||
|
||||
"create_subnet": "rule:admin_or_network_owner",
|
||||
"get_subnet": "rule:admin_or_owner or rule:shared",
|
||||
"update_subnet": "rule:admin_or_network_owner",
|
||||
"delete_subnet": "rule:admin_or_network_owner",
|
||||
|
||||
"create_network": "",
|
||||
"get_network": "rule:admin_or_owner or rule:shared or rule:external",
|
||||
"get_network:router:external": "rule:regular_user",
|
||||
"get_network:segments": "rule:admin_only",
|
||||
"get_network:provider:network_type": "rule:admin_only",
|
||||
"get_network:provider:physical_network": "rule:admin_only",
|
||||
"get_network:provider:segmentation_id": "rule:admin_only",
|
||||
"get_network:queue_id": "rule:admin_only",
|
||||
"create_network:shared": "rule:admin_only",
|
||||
"create_network:router:external": "rule:admin_only",
|
||||
"create_network:segments": "rule:admin_only",
|
||||
"create_network:provider:network_type": "rule:admin_only",
|
||||
"create_network:provider:physical_network": "rule:admin_only",
|
||||
"create_network:provider:segmentation_id": "rule:admin_only",
|
||||
"update_network": "rule:admin_or_owner",
|
||||
"update_network:segments": "rule:admin_only",
|
||||
"update_network:shared": "rule:admin_only",
|
||||
"update_network:provider:network_type": "rule:admin_only",
|
||||
"update_network:provider:physical_network": "rule:admin_only",
|
||||
"update_network:provider:segmentation_id": "rule:admin_only",
|
||||
"delete_network": "rule:admin_or_owner",
|
||||
|
||||
"create_port": "",
|
||||
"create_port:mac_address": "rule:admin_or_network_owner",
|
||||
"create_port:fixed_ips": "rule:admin_or_network_owner",
|
||||
"create_port:port_security_enabled": "rule:admin_or_network_owner",
|
||||
"create_port:binding:host_id": "rule:admin_only",
|
||||
"create_port:binding:profile": "rule:admin_only",
|
||||
"create_port:mac_learning_enabled": "rule:admin_or_network_owner",
|
||||
"get_port": "rule:admin_or_owner",
|
||||
"get_port:queue_id": "rule:admin_only",
|
||||
"get_port:binding:vif_type": "rule:admin_only",
|
||||
"get_port:binding:vif_details": "rule:admin_only",
|
||||
"get_port:binding:host_id": "rule:admin_only",
|
||||
"get_port:binding:profile": "rule:admin_only",
|
||||
"update_port": "rule:admin_or_owner",
|
||||
"update_port:fixed_ips": "rule:admin_or_network_owner",
|
||||
"update_port:port_security_enabled": "rule:admin_or_network_owner",
|
||||
"update_port:binding:host_id": "rule:admin_only",
|
||||
"update_port:binding:profile": "rule:admin_only",
|
||||
"update_port:mac_learning_enabled": "rule:admin_or_network_owner",
|
||||
"delete_port": "rule:admin_or_owner",
|
||||
|
||||
"create_router:external_gateway_info:enable_snat": "rule:admin_only",
|
||||
"update_router:external_gateway_info:enable_snat": "rule:admin_only",
|
||||
|
||||
"create_firewall": "",
|
||||
"get_firewall": "rule:admin_or_owner",
|
||||
"create_firewall:shared": "rule:admin_only",
|
||||
"get_firewall:shared": "rule:admin_only",
|
||||
"update_firewall": "rule:admin_or_owner",
|
||||
"update_firewall:shared": "rule:admin_only",
|
||||
"delete_firewall": "rule:admin_or_owner",
|
||||
|
||||
"create_firewall_policy": "",
|
||||
"get_firewall_policy": "rule:admin_or_owner or rule:shared_firewalls",
|
||||
"create_firewall_policy:shared": "rule:admin_or_owner",
|
||||
"update_firewall_policy": "rule:admin_or_owner",
|
||||
"delete_firewall_policy": "rule:admin_or_owner",
|
||||
|
||||
"create_firewall_rule": "",
|
||||
"get_firewall_rule": "rule:admin_or_owner or rule:shared_firewalls",
|
||||
"update_firewall_rule": "rule:admin_or_owner",
|
||||
"delete_firewall_rule": "rule:admin_or_owner",
|
||||
|
||||
"create_qos_queue": "rule:admin_only",
|
||||
"get_qos_queue": "rule:admin_only",
|
||||
|
||||
"update_agent": "rule:admin_only",
|
||||
"delete_agent": "rule:admin_only",
|
||||
"get_agent": "rule:admin_only",
|
||||
|
||||
"create_dhcp-network": "rule:admin_only",
|
||||
"delete_dhcp-network": "rule:admin_only",
|
||||
"get_dhcp-networks": "rule:admin_only",
|
||||
"create_l3-router": "rule:admin_only",
|
||||
"delete_l3-router": "rule:admin_only",
|
||||
"get_l3-routers": "rule:admin_only",
|
||||
"get_dhcp-agents": "rule:admin_only",
|
||||
"get_l3-agents": "rule:admin_only",
|
||||
"get_loadbalancer-agent": "rule:admin_only",
|
||||
"get_loadbalancer-pools": "rule:admin_only",
|
||||
|
||||
"create_router": "rule:regular_user",
|
||||
"get_router": "rule:admin_or_owner",
|
||||
"update_router:add_router_interface": "rule:admin_or_owner",
|
||||
"update_router:remove_router_interface": "rule:admin_or_owner",
|
||||
"delete_router": "rule:admin_or_owner",
|
||||
|
||||
"create_floatingip": "rule:regular_user",
|
||||
"update_floatingip": "rule:admin_or_owner",
|
||||
"delete_floatingip": "rule:admin_or_owner",
|
||||
"get_floatingip": "rule:admin_or_owner",
|
||||
|
||||
"create_network_profile": "rule:admin_only",
|
||||
"update_network_profile": "rule:admin_only",
|
||||
"delete_network_profile": "rule:admin_only",
|
||||
"get_network_profiles": "",
|
||||
"get_network_profile": "",
|
||||
"update_policy_profiles": "rule:admin_only",
|
||||
"get_policy_profiles": "",
|
||||
"get_policy_profile": "",
|
||||
|
||||
"create_metering_label": "rule:admin_only",
|
||||
"delete_metering_label": "rule:admin_only",
|
||||
"get_metering_label": "rule:admin_only",
|
||||
|
||||
"create_metering_label_rule": "rule:admin_only",
|
||||
"delete_metering_label_rule": "rule:admin_only",
|
||||
"get_metering_label_rule": "rule:admin_only",
|
||||
|
||||
"get_service_provider": "rule:regular_user",
|
||||
"get_lsn": "rule:admin_only",
|
||||
"create_lsn": "rule:admin_only"
|
||||
"get_vim": "rule:admin_or_owner or rule:shared"
|
||||
}
|
||||
|
@ -61,7 +61,8 @@ lock_path = $state_path/lock
|
||||
#
|
||||
# service_plugins =
|
||||
# Example: service_plugins = router,firewall,lbaas,vpnaas,metering
|
||||
service_plugins = tacker.vm.plugin.VNFMPlugin
|
||||
|
||||
service_plugins = vnfm,nfvo
|
||||
|
||||
# Paste configuration file
|
||||
# api_paste_config = api-paste.ini
|
||||
@ -395,6 +396,15 @@ auth_uri = http://127.0.0.1:5000
|
||||
# Specify drivers for monitoring
|
||||
# monitor_driver = ping, http_ping
|
||||
|
||||
[nfvo_vim]
|
||||
# Supported VIM drivers, resource orchestration controllers such as OpenStack, kvm
|
||||
#Default VIM driver is OpenStack
|
||||
#vim_drivers = openstack
|
||||
#Default VIM placement if vim id is not provided
|
||||
default_vim = VIM0
|
||||
|
||||
[vim_keys]
|
||||
#openstack = /etc/tacker/vim/fernet_keys
|
||||
[tacker_nova]
|
||||
# parameters for novaclient to talk to nova
|
||||
region_name = RegionOne
|
||||
|
@ -30,3 +30,4 @@ oslosphinx!=3.4.0,>=2.5.0 # Apache-2.0
|
||||
python-novaclient!=2.33.0,>=2.29.0 # Apache-2.0
|
||||
tosca-parser>=0.4.0 # Apache-2.0
|
||||
heat-translator>=0.4.0 # Apache-2.0
|
||||
cryptography>=1.0 # BSD/Apache-2.0
|
||||
|
@ -42,6 +42,9 @@ console_scripts =
|
||||
tacker.service_plugins =
|
||||
dummy = tacker.tests.unit.dummy_plugin:DummyServicePlugin
|
||||
vnfm = tacker.vm.plugin:VNFMPlugin
|
||||
nfvo = tacker.nfvo.nfvo_plugin:NfvoPlugin
|
||||
tacker.nfvo.vim.drivers =
|
||||
openstack = tacker.nfvo.drivers.vim.openstack_driver:OpenStack_Driver
|
||||
tacker.openstack.common.cache.backends =
|
||||
memory = tacker.openstack.common.cache._backends.memory:MemoryBackend
|
||||
tacker.tacker.device.drivers =
|
||||
|
@ -11,48 +11,33 @@
|
||||
# under the License.
|
||||
|
||||
from heatclient import client as heatclient
|
||||
from keystoneclient.v2_0 import client as ks_client
|
||||
from oslo_config import cfg
|
||||
|
||||
CONF = cfg.CONF
|
||||
|
||||
OPTS = [
|
||||
cfg.StrOpt('heat_uri',
|
||||
default='http://localhost:8004/v1',
|
||||
help=_("Heat service URI to create VNF resources"
|
||||
"specified in the VNFD templates")),
|
||||
]
|
||||
CONF.register_opts(OPTS, group='tacker_heat')
|
||||
from tacker.vm import keystone
|
||||
|
||||
|
||||
class OpenstackClients(object):
|
||||
|
||||
def __init__(self):
|
||||
def __init__(self, auth_attr, region_name=None):
|
||||
super(OpenstackClients, self).__init__()
|
||||
self.keystone_client = None
|
||||
self.keystone_plugin = keystone.Keystone()
|
||||
self.heat_client = None
|
||||
self.nova_client = None
|
||||
self.auth_url = CONF.keystone_authtoken.auth_uri + '/v2.0'
|
||||
self.auth_username = CONF.keystone_authtoken.username
|
||||
self.auth_password = CONF.keystone_authtoken.password
|
||||
self.auth_tenant_name = CONF.keystone_authtoken.project_name
|
||||
self.keystone_client = None
|
||||
self.region_name = region_name
|
||||
self.auth_attr = auth_attr
|
||||
|
||||
def _keystone_client(self):
|
||||
return ks_client.Client(
|
||||
tenant_name=self.auth_tenant_name,
|
||||
username=self.auth_username,
|
||||
password=self.auth_password,
|
||||
auth_url=self.auth_url)
|
||||
version = self.auth_attr['auth_url'].rpartition('/')[2]
|
||||
return self.keystone_plugin.initialize_client(version,
|
||||
**self.auth_attr)
|
||||
|
||||
def _heat_client(self):
|
||||
tenant_id = self.auth_token['tenant_id']
|
||||
token = self.auth_token['id']
|
||||
endpoint = CONF.tacker_heat.heat_uri + '/' + tenant_id
|
||||
return heatclient.Client('1', endpoint=endpoint, token=token)
|
||||
endpoint = self.keystone_session.get_endpoint(
|
||||
service_type='orchestration', region_name=self.region_name)
|
||||
return heatclient.Client('1', endpoint=endpoint,
|
||||
session=self.keystone_session)
|
||||
|
||||
@property
|
||||
def auth_token(self):
|
||||
return self.keystone.service_catalog.get_token()
|
||||
def keystone_session(self):
|
||||
return self.keystone.session
|
||||
|
||||
@property
|
||||
def keystone(self):
|
||||
|
@ -27,13 +27,17 @@ import os
|
||||
import random
|
||||
import signal
|
||||
import socket
|
||||
import sys
|
||||
import uuid
|
||||
|
||||
from eventlet.green import subprocess
|
||||
import netaddr
|
||||
from oslo_config import cfg
|
||||
from oslo_utils import importutils
|
||||
from stevedore import driver
|
||||
|
||||
from tacker.common import constants as q_const
|
||||
from tacker.i18n import _LE
|
||||
from tacker.openstack.common import lockutils
|
||||
from tacker.openstack.common import log as logging
|
||||
|
||||
@ -63,7 +67,7 @@ MEM_UNITS = {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CONF = cfg.CONF
|
||||
synchronized = lockutils.synchronized_with_prefix(SYNCHRONIZED_PREFIX)
|
||||
|
||||
|
||||
@ -347,3 +351,44 @@ def change_memory_unit(mem, to):
|
||||
return eval(mem_arr[0] +
|
||||
MEM_UNITS[unit][to]["op"] +
|
||||
MEM_UNITS[unit][to]["val"])
|
||||
|
||||
|
||||
def load_class_by_alias_or_classname(namespace, name):
|
||||
"""Load class using stevedore alias or the class name
|
||||
|
||||
Load class using the stevedore driver manager
|
||||
:param namespace: namespace where the alias is defined
|
||||
:param name: alias or class name of the class to be loaded
|
||||
:returns class if calls can be loaded
|
||||
:raises ImportError if class cannot be loaded
|
||||
"""
|
||||
|
||||
if not name:
|
||||
LOG.error(_LE("Alias or class name is not set"))
|
||||
raise ImportError(_("Class not found."))
|
||||
try:
|
||||
# Try to resolve class by alias
|
||||
mgr = driver.DriverManager(namespace, name)
|
||||
class_to_load = mgr.driver
|
||||
except RuntimeError:
|
||||
e1_info = sys.exc_info()
|
||||
# Fallback to class name
|
||||
try:
|
||||
class_to_load = importutils.import_class(name)
|
||||
except (ImportError, ValueError):
|
||||
LOG.error(_LE("Error loading class by alias"),
|
||||
exc_info=e1_info)
|
||||
LOG.error(_LE("Error loading class by class name"),
|
||||
exc_info=True)
|
||||
raise ImportError(_("Class not found."))
|
||||
return class_to_load
|
||||
|
||||
|
||||
def deep_update(orig_dict, new_dict):
|
||||
for key, value in new_dict.items():
|
||||
if isinstance(value, dict):
|
||||
if key in orig_dict and isinstance(orig_dict[key], dict):
|
||||
deep_update(orig_dict[key], value)
|
||||
continue
|
||||
|
||||
orig_dict[key] = value
|
||||
|
@ -0,0 +1,68 @@
|
||||
# Copyright 2016 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.
|
||||
#
|
||||
|
||||
"""multisite_vim
|
||||
|
||||
Revision ID: 5246a6bd410f
|
||||
Revises: 24bec5f211c7
|
||||
Create Date: 2016-03-22 14:05:15.129330
|
||||
|
||||
"""
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '5246a6bd410f'
|
||||
down_revision = '24bec5f211c7'
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
def upgrade(active_plugins=None, options=None):
|
||||
op.create_table('vims',
|
||||
sa.Column('id', sa.String(length=255), nullable=False),
|
||||
sa.Column('type', sa.String(length=255), nullable=False),
|
||||
sa.Column('tenant_id', sa.String(length=255), nullable=True),
|
||||
sa.Column('name', sa.String(length=255), nullable=True),
|
||||
sa.Column('description', sa.String(length=255), nullable=True),
|
||||
sa.Column('placement_attr', sa.PickleType(), nullable=True),
|
||||
sa.Column('shared', sa.Boolean(), server_default=sa.text(u'true'),
|
||||
nullable=False),
|
||||
sa.PrimaryKeyConstraint('id'),
|
||||
mysql_engine='InnoDB'
|
||||
)
|
||||
op.create_table('vimauths',
|
||||
sa.Column('id', sa.String(length=36), nullable=False),
|
||||
sa.Column('vim_id', sa.String(length=255), nullable=False),
|
||||
sa.Column('password', sa.String(length=128), nullable=False),
|
||||
sa.Column('auth_url', sa.String(length=255), nullable=False),
|
||||
sa.Column('vim_project', sa.PickleType(), nullable=False),
|
||||
sa.Column('auth_cred', sa.PickleType(), nullable=False),
|
||||
sa.ForeignKeyConstraint(['vim_id'], ['vims.id'], ),
|
||||
sa.PrimaryKeyConstraint('id'),
|
||||
sa.UniqueConstraint('auth_url')
|
||||
)
|
||||
op.add_column(u'devices', sa.Column('placement_attr', sa.PickleType(),
|
||||
nullable=True))
|
||||
op.add_column(u'devices', sa.Column('vim_id', sa.String(length=36),
|
||||
nullable=False))
|
||||
op.create_foreign_key(None, 'devices', 'vims', ['vim_id'], ['id'])
|
||||
|
||||
|
||||
def downgrade(active_plugins=None, options=None):
|
||||
op.drop_constraint(None, 'devices', type_='foreignkey')
|
||||
op.drop_column(u'devices', 'vim_id')
|
||||
op.drop_column(u'devices', 'placement_attr')
|
||||
op.drop_table('vimauths')
|
||||
op.drop_table('vims')
|
@ -1 +1 @@
|
||||
24bec5f211c7
|
||||
5246a6bd410f
|
@ -22,6 +22,7 @@ Based on this comparison database can be healed with healing migration.
|
||||
"""
|
||||
|
||||
from tacker.db import model_base
|
||||
from tacker.db.nfvo import nfvo_db # noqa
|
||||
from tacker.db.vm import proxy_db # noqa
|
||||
from tacker.db.vm import vm_db # noqa
|
||||
|
||||
|
0
tacker/db/nfvo/__init__.py
Normal file
0
tacker/db/nfvo/__init__.py
Normal file
169
tacker/db/nfvo/nfvo_db.py
Normal file
169
tacker/db/nfvo/nfvo_db.py
Normal file
@ -0,0 +1,169 @@
|
||||
# Copyright 2016 Brocade Communications System, Inc.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
#
|
||||
# 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
|
||||
|
||||
import sqlalchemy as sa
|
||||
from sqlalchemy import orm
|
||||
from sqlalchemy.orm import exc as orm_exc
|
||||
from sqlalchemy import sql
|
||||
|
||||
from tacker.db import api as tdbapi
|
||||
from tacker.db import db_base
|
||||
from tacker.db import model_base
|
||||
from tacker.db import models_v1
|
||||
from tacker.db.vm import vm_db
|
||||
from tacker.extensions import nfvo
|
||||
from tacker import manager
|
||||
from tacker.openstack.common.db import exception
|
||||
from tacker.openstack.common import uuidutils
|
||||
|
||||
|
||||
VIM_ATTRIBUTES = ('id', 'type', 'tenant_id', 'name', 'description',
|
||||
'placement_attr', 'shared')
|
||||
VIM_AUTH_ATTRIBUTES = ('auth_url', 'vim_project', 'password', 'auth_cred')
|
||||
|
||||
|
||||
class Vim(model_base.BASE, models_v1.HasTenant):
|
||||
id = sa.Column(sa.String(255),
|
||||
primary_key=True,
|
||||
default=uuidutils.generate_uuid)
|
||||
type = sa.Column(sa.String(255), nullable=False)
|
||||
tenant_id = sa.Column(sa.String(255), nullable=True)
|
||||
name = sa.Column(sa.String(255), nullable=True)
|
||||
description = sa.Column(sa.String(255), nullable=True)
|
||||
placement_attr = sa.Column(sa.PickleType, nullable=True)
|
||||
shared = sa.Column(sa.Boolean, default=True, server_default=sql.true(
|
||||
), nullable=False)
|
||||
vim_auth = orm.relationship('VimAuth')
|
||||
|
||||
|
||||
class VimAuth(model_base.BASE, models_v1.HasId):
|
||||
vim_id = sa.Column(sa.String(255), sa.ForeignKey('vims.id'),
|
||||
nullable=False)
|
||||
password = sa.Column(sa.String(128), nullable=False)
|
||||
auth_url = sa.Column(sa.String(255), nullable=False)
|
||||
vim_project = sa.Column(sa.PickleType, nullable=False)
|
||||
auth_cred = sa.Column(sa.PickleType, nullable=False)
|
||||
__table_args__ = (sa.UniqueConstraint('auth_url'), {})
|
||||
|
||||
|
||||
class NfvoPluginDb(nfvo.NFVOPluginBase, db_base.CommonDbMixin):
|
||||
|
||||
def __init__(self):
|
||||
tdbapi.register_models()
|
||||
super(NfvoPluginDb, self).__init__()
|
||||
|
||||
@property
|
||||
def _core_plugin(self):
|
||||
return manager.TackerManager.get_plugin()
|
||||
|
||||
def _make_vim_dict(self, vim_db, fields=None):
|
||||
res = dict((key, vim_db[key]) for key in VIM_ATTRIBUTES)
|
||||
vim_auth_db = vim_db.vim_auth
|
||||
res['auth_url'] = vim_auth_db[0].auth_url
|
||||
res['vim_project'] = vim_auth_db[0].vim_project
|
||||
res['auth_cred'] = vim_auth_db[0].auth_cred
|
||||
res['auth_cred']['password'] = vim_auth_db[0].password
|
||||
return self._fields(res, fields)
|
||||
|
||||
def _fields(self, resource, fields):
|
||||
if fields:
|
||||
return dict(((key, item) for key, item in resource.items()
|
||||
if key in fields))
|
||||
return resource
|
||||
|
||||
def _get_resource(self, context, model, id):
|
||||
try:
|
||||
return self._get_by_id(context, model, id)
|
||||
except orm_exc.NoResultFound:
|
||||
if issubclass(model, Vim):
|
||||
raise nfvo.VimNotFoundException(vim_id=id)
|
||||
else:
|
||||
raise
|
||||
|
||||
def create_vim(self, context, vim):
|
||||
vim_cred = vim['auth_cred']
|
||||
try:
|
||||
with context.session.begin(subtransactions=True):
|
||||
vim_db = Vim(
|
||||
id=vim.get('id'),
|
||||
type=vim.get('type'),
|
||||
tenant_id=vim.get('tenant_id'),
|
||||
name=vim.get('name'),
|
||||
description=vim.get('description'),
|
||||
placement_attr=vim.get('placement_attr'))
|
||||
context.session.add(vim_db)
|
||||
vim_auth_db = VimAuth(
|
||||
id=str(uuid.uuid4()),
|
||||
vim_id=vim.get('id'),
|
||||
password=vim_cred.pop('password'),
|
||||
vim_project=vim.get('vim_project'),
|
||||
auth_url=vim.get('auth_url'),
|
||||
auth_cred=vim_cred)
|
||||
context.session.add(vim_auth_db)
|
||||
except exception.DBDuplicateEntry:
|
||||
raise nfvo.VimDuplicateUrlException()
|
||||
return self._make_vim_dict(vim_db)
|
||||
|
||||
def delete_vim(self, context, vim_id):
|
||||
with context.session.begin(subtransactions=True):
|
||||
vim_db = self._get_resource(context, Vim, vim_id)
|
||||
context.session.query(VimAuth).filter_by(
|
||||
vim_id=vim_id).delete()
|
||||
context.session.delete(vim_db)
|
||||
|
||||
def is_vim_still_in_use(self, context, vim_id):
|
||||
with context.session.begin(subtransactions=True):
|
||||
devices_db = context.session.query(vm_db.Device).filter_by(
|
||||
vim_id=vim_id).first()
|
||||
if devices_db is not None:
|
||||
raise nfvo.VimInUseException(vim_id=vim_id)
|
||||
return devices_db
|
||||
|
||||
def get_vim(self, context, vim_id, fields=None):
|
||||
vim_db = self._get_resource(context, Vim, vim_id)
|
||||
return self._make_vim_dict(vim_db)
|
||||
|
||||
def get_vims(self, context, filters=None, fields=None):
|
||||
return self._get_collection(context, Vim, self._make_vim_dict,
|
||||
filters=filters, fields=fields)
|
||||
|
||||
def update_vim(self, context, vim_id, vim):
|
||||
with context.session.begin(subtransactions=True):
|
||||
vim_cred = vim['auth_cred']
|
||||
vim_project = vim['vim_project']
|
||||
try:
|
||||
vim_auth_db = (self._model_query(context, VimAuth).filter(
|
||||
VimAuth.vim_id == vim_id).with_lockmode('update').one())
|
||||
except orm_exc.NoResultFound:
|
||||
raise nfvo.VimNotFound(vim_id=vim_id)
|
||||
vim_auth_db.update({'auth_cred': vim_cred, 'password':
|
||||
vim_cred.pop('password'), 'vim_project':
|
||||
vim_project})
|
||||
return self.get_vim(context, vim_id)
|
||||
|
||||
def get_vim_by_name(self, context, vim_name, fields=None):
|
||||
vim_db = self._get_by_name(context, Vim, vim_name)
|
||||
return self._make_vim_dict(vim_db)
|
||||
|
||||
def _get_by_name(self, context, model, name):
|
||||
try:
|
||||
query = self._model_query(context, model)
|
||||
return query.filter(model.name == name).one()
|
||||
except orm_exc.NoResultFound:
|
||||
if issubclass(model, Vim):
|
||||
raise
|
@ -120,6 +120,9 @@ class Device(model_base.BASE, models_v1.HasTenant):
|
||||
attributes = orm.relationship("DeviceAttribute", backref="device")
|
||||
|
||||
status = sa.Column(sa.String(255), nullable=False)
|
||||
vim_id = sa.Column(sa.String(36), sa.ForeignKey('vims.id'), nullable=False)
|
||||
placement_attr = sa.Column(sa.PickleType, nullable=True)
|
||||
vim = orm.relationship('Vim')
|
||||
|
||||
|
||||
class DeviceAttribute(model_base.BASE, models_v1.HasId):
|
||||
@ -195,7 +198,8 @@ class VNFMPluginDb(vnfm.VNFMPluginBase, db_base.CommonDbMixin):
|
||||
'attributes': self._make_dev_attrs_dict(device_db.attributes),
|
||||
}
|
||||
key_list = ('id', 'tenant_id', 'name', 'description', 'instance_id',
|
||||
'template_id', 'status', 'mgmt_url')
|
||||
'vim_id', 'placement_attr', 'template_id', 'status',
|
||||
'mgmt_url')
|
||||
res.update((key, device_db[key]) for key in key_list)
|
||||
return self._fields(res, fields)
|
||||
|
||||
@ -335,13 +339,14 @@ class VNFMPluginDb(vnfm.VNFMPluginBase, db_base.CommonDbMixin):
|
||||
|
||||
# called internally, not by REST API
|
||||
def _create_device_pre(self, context, device):
|
||||
device = device['device']
|
||||
LOG.debug(_('device %s'), device)
|
||||
tenant_id = self._get_tenant_id_for_create(context, device)
|
||||
template_id = device['template_id']
|
||||
name = device.get('name')
|
||||
device_id = device.get('id') or str(uuid.uuid4())
|
||||
attributes = device.get('attributes', {})
|
||||
vim_id = device.get('vim_id')
|
||||
placement_attr = device.get('placement_attr', {})
|
||||
with context.session.begin(subtransactions=True):
|
||||
template_db = self._get_resource(context, DeviceTemplate,
|
||||
template_id)
|
||||
@ -351,13 +356,15 @@ class VNFMPluginDb(vnfm.VNFMPluginBase, db_base.CommonDbMixin):
|
||||
description=template_db.description,
|
||||
instance_id=None,
|
||||
template_id=template_id,
|
||||
vim_id=vim_id,
|
||||
placement_attr=placement_attr,
|
||||
status=constants.PENDING_CREATE)
|
||||
context.session.add(device_db)
|
||||
for key, value in attributes.items():
|
||||
arg = DeviceAttribute(
|
||||
id=str(uuid.uuid4()), device_id=device_id,
|
||||
key=key, value=value)
|
||||
context.session.add(arg)
|
||||
arg = DeviceAttribute(
|
||||
id=str(uuid.uuid4()), device_id=device_id,
|
||||
key=key, value=value)
|
||||
context.session.add(arg)
|
||||
|
||||
return self._make_device_dict(device_db)
|
||||
|
||||
@ -376,8 +383,10 @@ class VNFMPluginDb(vnfm.VNFMPluginBase, db_base.CommonDbMixin):
|
||||
query.update({'status': constants.ERROR})
|
||||
|
||||
for (key, value) in device_dict['attributes'].items():
|
||||
self._device_attribute_update_or_create(context, device_id,
|
||||
key, value)
|
||||
# do not store decrypted vim auth in device attr table
|
||||
if 'vim_auth' not in key:
|
||||
self._device_attribute_update_or_create(context, device_id,
|
||||
key, value)
|
||||
|
||||
def _create_device_status(self, context, device_id, new_status):
|
||||
with context.session.begin(subtransactions=True):
|
||||
@ -421,7 +430,8 @@ class VNFMPluginDb(vnfm.VNFMPluginBase, db_base.CommonDbMixin):
|
||||
delete(synchronize_session='fetch'))
|
||||
|
||||
for (key, value) in dev_attrs.items():
|
||||
self._device_attribute_update_or_create(context, device_id,
|
||||
if 'vim_auth' not in key:
|
||||
self._device_attribute_update_or_create(context, device_id,
|
||||
key, value)
|
||||
|
||||
def _delete_device_pre(self, context, device_id):
|
||||
@ -532,7 +542,9 @@ class VNFMPluginDb(vnfm.VNFMPluginBase, db_base.CommonDbMixin):
|
||||
description=device_db.description,
|
||||
instance_id=device_db.instance_id,
|
||||
mgmt_url=device_db.mgmt_url,
|
||||
status=device_db.status)
|
||||
status=device_db.status,
|
||||
vim_id=device_db.vim_id,
|
||||
placement_attr=device_db.placement_attr)
|
||||
context.session.add(new_device_db)
|
||||
|
||||
(self._model_query(context, DeviceAttribute).
|
||||
|
207
tacker/extensions/nfvo.py
Normal file
207
tacker/extensions/nfvo.py
Normal file
@ -0,0 +1,207 @@
|
||||
# Copyright 2016 Brocade Communications Systems Inc
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# 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 abc
|
||||
|
||||
import six
|
||||
|
||||
from tacker.api import extensions
|
||||
from tacker.api.v1 import attributes as attr
|
||||
from tacker.api.v1 import resource_helper
|
||||
from tacker.common import exceptions
|
||||
from tacker.plugins.common import constants
|
||||
from tacker.services import service_base
|
||||
|
||||
|
||||
class VimUnauthorizedException(exceptions.TackerException):
|
||||
message = _("%(message)s")
|
||||
|
||||
|
||||
class VimConnectionException(exceptions.TackerException):
|
||||
message = _("%(message)s")
|
||||
|
||||
|
||||
class VimInUseException(exceptions.TackerException):
|
||||
message = _("VIM %(vim_id)s is still in use by VNF")
|
||||
|
||||
|
||||
class VimDefaultIdException(exceptions.TackerException):
|
||||
message = _("Default VIM name %(vim_name)s is invalid or there are "
|
||||
"multiple VIM matches found. Please specify a valid default "
|
||||
"VIM in tacker.conf")
|
||||
|
||||
|
||||
class VimNotFoundException(exceptions.TackerException):
|
||||
message = _("Specified VIM id %(vim_id)s is invalid. Please verify and "
|
||||
"pass a valid VIM id")
|
||||
|
||||
|
||||
class VimRegionNotFoundException(exceptions.TackerException):
|
||||
message = _("Unknown VIM region name %(region_name)s")
|
||||
|
||||
|
||||
class VimKeyNotFoundException(exceptions.TackerException):
|
||||
message = _("Unable to find key file for VIM %(vim_id)s")
|
||||
|
||||
|
||||
class VimDuplicateUrlException(exceptions.TackerException):
|
||||
message = _("VIM with specified auth URL already exists. Cannot register "
|
||||
"duplicate VIM")
|
||||
|
||||
RESOURCE_ATTRIBUTE_MAP = {
|
||||
|
||||
'vims': {
|
||||
'id': {
|
||||
'allow_post': False,
|
||||
'allow_put': False,
|
||||
'validate': {'type:uuid': None},
|
||||
'is_visible': True,
|
||||
'primary_key': True,
|
||||
},
|
||||
'tenant_id': {
|
||||
'allow_post': True,
|
||||
'allow_put': False,
|
||||
'validate': {'type:string': None},
|
||||
'required_by_policy': True,
|
||||
'is_visible': True
|
||||
},
|
||||
'type': {
|
||||
'allow_post': True,
|
||||
'allow_put': False,
|
||||
'validate': {'type:string': None},
|
||||
'is_visible': True
|
||||
},
|
||||
'auth_url': {
|
||||
'allow_post': True,
|
||||
'allow_put': False,
|
||||
'validate': {'type:string': None},
|
||||
'is_visible': True
|
||||
},
|
||||
'auth_cred': {
|
||||
'allow_post': True,
|
||||
'allow_put': True,
|
||||
'validate': {'type:dict_or_nodata': None},
|
||||
'is_visible': True,
|
||||
},
|
||||
'vim_project': {
|
||||
'allow_post': True,
|
||||
'allow_put': True,
|
||||
'validate': {'type:dict_or_nodata': None},
|
||||
'is_visible': True,
|
||||
},
|
||||
'name': {
|
||||
'allow_post': True,
|
||||
'allow_put': True,
|
||||
'validate': {'type:string': None},
|
||||
'is_visible': True,
|
||||
'default': '',
|
||||
},
|
||||
'description': {
|
||||
'allow_post': True,
|
||||
'allow_put': True,
|
||||
'validate': {'type:string': None},
|
||||
'is_visible': True,
|
||||
'default': '',
|
||||
},
|
||||
'placement_attr': {
|
||||
'allow_post': False,
|
||||
'allow_put': False,
|
||||
'is_visible': True,
|
||||
'default': None,
|
||||
},
|
||||
'shared': {
|
||||
'allow_post': False,
|
||||
'allow_put': False,
|
||||
'is_visible': False,
|
||||
'convert_to': attr.convert_to_boolean,
|
||||
'required_by_policy': True
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class Nfvo(extensions.ExtensionDescriptor):
|
||||
@classmethod
|
||||
def get_name(cls):
|
||||
return 'NFVO'
|
||||
|
||||
@classmethod
|
||||
def get_alias(cls):
|
||||
return 'NFV Orchestrator'
|
||||
|
||||
@classmethod
|
||||
def get_description(cls):
|
||||
return "Extension for NFV Orchestrator"
|
||||
|
||||
@classmethod
|
||||
def get_namespace(cls):
|
||||
return 'http://wiki.openstack.org/Tacker'
|
||||
|
||||
@classmethod
|
||||
def get_updated(cls):
|
||||
return "2015-12-21T10:00:00-00:00"
|
||||
|
||||
@classmethod
|
||||
def get_resources(cls):
|
||||
special_mappings = {}
|
||||
plural_mappings = resource_helper.build_plural_mappings(
|
||||
special_mappings, RESOURCE_ATTRIBUTE_MAP)
|
||||
attr.PLURALS.update(plural_mappings)
|
||||
return resource_helper.build_resource_info(
|
||||
plural_mappings, RESOURCE_ATTRIBUTE_MAP, constants.NFVO,
|
||||
translate_name=True)
|
||||
|
||||
@classmethod
|
||||
def get_plugin_interface(cls):
|
||||
return NFVOPluginBase
|
||||
|
||||
def update_attributes_map(self, attributes):
|
||||
super(Nfvo, self).update_attributes_map(
|
||||
attributes, extension_attrs_map=RESOURCE_ATTRIBUTE_MAP)
|
||||
|
||||
def get_extended_resources(self, version):
|
||||
version_map = {'1.0': RESOURCE_ATTRIBUTE_MAP}
|
||||
return version_map.get(version, {})
|
||||
|
||||
|
||||
@six.add_metaclass(abc.ABCMeta)
|
||||
class NFVOPluginBase(service_base.NFVPluginBase):
|
||||
def get_plugin_name(self):
|
||||
return constants.NFVO
|
||||
|
||||
def get_plugin_type(self):
|
||||
return constants.NFVO
|
||||
|
||||
def get_plugin_description(self):
|
||||
return 'Tacker NFV Orchestrator plugin'
|
||||
|
||||
@abc.abstractmethod
|
||||
def create_vim(self, context, vim):
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def delete_vim(self, context, vim_id):
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def get_vim(self, context, vim_id, fields=None):
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def get_vims(self, context, filters=None, fields=None):
|
||||
pass
|
||||
|
||||
def get_vim_by_name(self, context, vim_name, fields=None):
|
||||
raise NotImplementedError()
|
@ -23,7 +23,7 @@ from tacker.api.v1 import resource_helper
|
||||
from tacker.common import exceptions
|
||||
from tacker.openstack.common import log as logging
|
||||
from tacker.plugins.common import constants
|
||||
from tacker.services.service_base import NFVPluginBase
|
||||
from tacker.services import service_base
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
@ -225,6 +225,13 @@ RESOURCE_ATTRIBUTE_MAP = {
|
||||
'validate': {'type:uuid': None},
|
||||
'is_visible': True,
|
||||
},
|
||||
'vim_id': {
|
||||
'allow_post': True,
|
||||
'allow_put': False,
|
||||
'validate': {'type:string': None},
|
||||
'is_visible': True,
|
||||
'default': '',
|
||||
},
|
||||
'name': {
|
||||
'allow_post': True,
|
||||
'allow_put': True,
|
||||
@ -258,6 +265,13 @@ RESOURCE_ATTRIBUTE_MAP = {
|
||||
'is_visible': True,
|
||||
'default': {},
|
||||
},
|
||||
'placement_attr': {
|
||||
'allow_post': True,
|
||||
'allow_put': False,
|
||||
'validate': {'type:dict_or_none': None},
|
||||
'is_visible': True,
|
||||
'default': {},
|
||||
},
|
||||
'status': {
|
||||
'allow_post': False,
|
||||
'allow_put': False,
|
||||
@ -313,7 +327,7 @@ class Vnfm(extensions.ExtensionDescriptor):
|
||||
|
||||
|
||||
@six.add_metaclass(abc.ABCMeta)
|
||||
class VNFMPluginBase(NFVPluginBase):
|
||||
class VNFMPluginBase(service_base.NFVPluginBase):
|
||||
def get_plugin_name(self):
|
||||
return constants.VNFM
|
||||
|
||||
|
@ -17,7 +17,6 @@ from oslo_config import cfg
|
||||
|
||||
from tacker.common import rpc_compat
|
||||
from tacker.common import utils
|
||||
from tacker.openstack.common import importutils
|
||||
from tacker.openstack.common import log as logging
|
||||
from tacker.openstack.common import periodic_task
|
||||
|
||||
@ -101,6 +100,27 @@ class TackerManager(object):
|
||||
self.service_plugins = {}
|
||||
self._load_service_plugins()
|
||||
|
||||
@staticmethod
|
||||
def load_class_for_provider(namespace, plugin_provider):
|
||||
"""Loads plugin using alias or class name
|
||||
|
||||
Load class using stevedore alias or the class name
|
||||
:param namespace: namespace where alias is defined
|
||||
:param plugin_provider: plugin alias or class name
|
||||
:returns plugin that is loaded
|
||||
:raises ImportError if fails to load plugin
|
||||
"""
|
||||
|
||||
try:
|
||||
return utils.load_class_by_alias_or_classname(namespace,
|
||||
plugin_provider)
|
||||
except ImportError:
|
||||
raise ImportError(_("Plugin '%s' not found.") % plugin_provider)
|
||||
|
||||
def _get_plugin_instance(self, namespace, plugin_provider):
|
||||
plugin_class = self.load_class_for_provider(namespace, plugin_provider)
|
||||
return plugin_class()
|
||||
|
||||
def _load_service_plugins(self):
|
||||
"""Loads service plugins.
|
||||
|
||||
@ -112,14 +132,10 @@ class TackerManager(object):
|
||||
for provider in plugin_providers:
|
||||
if provider == '':
|
||||
continue
|
||||
try:
|
||||
LOG.info(_("Loading Plugin: %s"), provider)
|
||||
plugin_class = importutils.import_class(provider)
|
||||
except ImportError:
|
||||
LOG.exception(_("Error loading plugin"))
|
||||
raise ImportError(_("Plugin not found."))
|
||||
plugin_inst = plugin_class()
|
||||
LOG.info(_("Loading Plugin: %s"), provider)
|
||||
|
||||
plugin_inst = self._get_plugin_instance('tacker.service_plugins',
|
||||
provider)
|
||||
# only one implementation of svc_type allowed
|
||||
# specifying more than one plugin
|
||||
# for the same type is a fatal exception
|
||||
@ -129,7 +145,6 @@ class TackerManager(object):
|
||||
plugin_inst.get_plugin_type())
|
||||
|
||||
self.service_plugins[plugin_inst.get_plugin_type()] = plugin_inst
|
||||
|
||||
# # search for possible agent notifiers declared in service plugin
|
||||
# # (needed by agent management extension)
|
||||
# if (hasattr(self.plugin, 'agent_notifiers') and
|
||||
|
0
tacker/nfvo/__init__.py
Normal file
0
tacker/nfvo/__init__.py
Normal file
0
tacker/nfvo/drivers/__init__.py
Normal file
0
tacker/nfvo/drivers/__init__.py
Normal file
0
tacker/nfvo/drivers/vim/__init__.py
Normal file
0
tacker/nfvo/drivers/vim/__init__.py
Normal file
84
tacker/nfvo/drivers/vim/abstract_vim_driver.py
Normal file
84
tacker/nfvo/drivers/vim/abstract_vim_driver.py
Normal file
@ -0,0 +1,84 @@
|
||||
# All Rights Reserved.
|
||||
#
|
||||
#
|
||||
# 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 abc
|
||||
|
||||
import six
|
||||
|
||||
from tacker.api import extensions
|
||||
|
||||
|
||||
@six.add_metaclass(abc.ABCMeta)
|
||||
class VimAbstractDriver(extensions.PluginInterface):
|
||||
|
||||
@abc.abstractmethod
|
||||
def get_type(self):
|
||||
"""Get VIM Driver type
|
||||
|
||||
Return one of predefined types of VIMs.
|
||||
"""
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def get_name(self):
|
||||
"""Get VIM name
|
||||
|
||||
Return a symbolic name for the VIM driver.
|
||||
"""
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def get_description(self):
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def register_vim(self, context, vim_obj):
|
||||
"""Register VIM object in to NFVO plugin
|
||||
|
||||
Validate, encode and store VIM information for deploying VNFs.
|
||||
"""
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def deregister_vim(self, context, vim_id):
|
||||
"""Deregister VIM object from NFVO plugin
|
||||
|
||||
Cleanup VIM data and delete VIM information
|
||||
"""
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def authenticate_vim(self, context, vim_obj):
|
||||
"""Authenticate VIM connection parameters
|
||||
|
||||
Validate authentication credentials and connectivity of VIM
|
||||
"""
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def encode_vim_auth(self, context, vim_id, auth):
|
||||
"""Encrypt VIM credentials
|
||||
|
||||
Encrypt and store VIM sensitive information such as password
|
||||
"""
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def delete_vim_auth(self, vim_id):
|
||||
"""Delete VIM auth keys
|
||||
|
||||
Delete VIM sensitive information such as keys from file system or DB
|
||||
"""
|
||||
pass
|
180
tacker/nfvo/drivers/vim/openstack_driver.py
Normal file
180
tacker/nfvo/drivers/vim/openstack_driver.py
Normal file
@ -0,0 +1,180 @@
|
||||
# Copyright 2016 Brocade Communications System, Inc.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
#
|
||||
# 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
|
||||
|
||||
from oslo_config import cfg
|
||||
|
||||
from tacker.common import log
|
||||
from tacker.extensions import nfvo
|
||||
from tacker.nfvo.drivers.vim import abstract_vim_driver
|
||||
from tacker.openstack.common import log as logging
|
||||
from tacker.vm import keystone
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
CONF = cfg.CONF
|
||||
|
||||
OPTS = [cfg.StrOpt('openstack', default='/etc/tacker/vim/fernet_keys',
|
||||
help='Dir.path to store fernet keys.')]
|
||||
cfg.CONF.register_opts(OPTS, 'vim_keys')
|
||||
|
||||
|
||||
class OpenStack_Driver(abstract_vim_driver.VimAbstractDriver):
|
||||
"""Driver for OpenStack VIM
|
||||
|
||||
OpenStack driver handles interactions with local as well as
|
||||
remote OpenStack instances. The driver invokes keystone service for VIM
|
||||
authorization and validation. The driver is also responsible for
|
||||
discovering placement attributes such as regions, availability zones
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.keystone = keystone.Keystone()
|
||||
self.keystone.create_key_dir(CONF.vim_keys.openstack)
|
||||
|
||||
def get_type(self):
|
||||
return 'openstack'
|
||||
|
||||
def get_name(self):
|
||||
return 'OpenStack VIM Driver'
|
||||
|
||||
def get_description(self):
|
||||
return 'OpenStack VIM Driver'
|
||||
|
||||
def authenticate_vim(self, vim_obj):
|
||||
"""Validate VIM auth attributes
|
||||
|
||||
Initialize keystoneclient with provided authentication attributes.
|
||||
"""
|
||||
auth_url = vim_obj['auth_url']
|
||||
auth_cred = vim_obj['auth_cred']
|
||||
vim_project = vim_obj['vim_project']
|
||||
keystone_version = self._validate_auth_url(auth_url)
|
||||
|
||||
if keystone_version not in auth_url:
|
||||
vim_obj['auth_url'] = auth_url + '/' + keystone_version
|
||||
if keystone_version == 'v3':
|
||||
auth_cred['project_id'] = vim_project.get('id', None)
|
||||
auth_cred['project_name'] = vim_project.get('name', None)
|
||||
if 'project_domain_id' not in auth_cred:
|
||||
auth_cred[
|
||||
'project_domain_id'
|
||||
] = CONF.keystone_authtoken.project_domain_id
|
||||
if 'user_domain_id' not in auth_cred:
|
||||
auth_cred[
|
||||
'user_domain_id'
|
||||
] = CONF.keystone_authtoken.user_domain_id
|
||||
else:
|
||||
auth_cred['tenant_id'] = vim_project.pop('id', None)
|
||||
auth_cred['tenant_name'] = vim_project.pop('name', None)
|
||||
# user_id is not supported in keystone v2
|
||||
auth_cred.pop('user_id', None)
|
||||
auth_cred['auth_url'] = vim_obj['auth_url']
|
||||
return self._initialize_keystone(keystone_version, auth_cred)
|
||||
|
||||
def _validate_auth_url(self, auth_url):
|
||||
try:
|
||||
keystone_version = self.keystone.get_version(auth_url)
|
||||
except Exception as e:
|
||||
LOG.error(_('VIM Auth URL invalid'))
|
||||
raise nfvo.VimConnectionException(message=e.message)
|
||||
return keystone_version
|
||||
|
||||
def _initialize_keystone(self, version, auth):
|
||||
try:
|
||||
ks_client = self.keystone.initialize_client(version=version,
|
||||
**auth)
|
||||
except Exception as e:
|
||||
LOG.error(_('VIM authentication failed'))
|
||||
raise nfvo.VimUnauthorizedException(message=e.message)
|
||||
return ks_client
|
||||
|
||||
def _find_regions(self, ks_client):
|
||||
if ks_client.version == 'v2.0':
|
||||
service_list = ks_client.services.list()
|
||||
heat_service_id = (service.id for service in
|
||||
service_list if service.type == 'orchestration')
|
||||
endpoints_list = ks_client.endpoints.list()
|
||||
region_list = [endpoint.region for endpoint in endpoints_list if
|
||||
endpoint.service_id == heat_service_id]
|
||||
else:
|
||||
region_info = ks_client.regions.list()
|
||||
region_list = [region.id for region in region_info]
|
||||
if not region_list:
|
||||
LOG.info(_('Unable to find VIM regions'))
|
||||
return
|
||||
return region_list
|
||||
|
||||
def discover_placement_attr(self, vim_obj, ks_client):
|
||||
"""Fetch VIM placement information
|
||||
|
||||
Attributes can include regions, AZ.
|
||||
"""
|
||||
regions_list = self._find_regions(ks_client)
|
||||
vim_obj['placement_attr'] = {'regions': regions_list}
|
||||
return vim_obj
|
||||
|
||||
@log.log
|
||||
def register_vim(self, vim_obj):
|
||||
"""Validate and register VIM
|
||||
|
||||
Store VIM information in Tacker for
|
||||
VNF placements
|
||||
"""
|
||||
ks_client = self.authenticate_vim(vim_obj)
|
||||
self.discover_placement_attr(vim_obj, ks_client)
|
||||
self.encode_vim_auth(vim_obj['id'], vim_obj['auth_cred'])
|
||||
LOG.debug(_('VIM registration complete %s'), vim_obj)
|
||||
|
||||
@log.log
|
||||
def deregister_vim(self, vim_id):
|
||||
"""Deregister VIM from NFVO
|
||||
|
||||
Delete VIM keys from file system
|
||||
"""
|
||||
self.delete_vim_auth(vim_id)
|
||||
|
||||
@log.log
|
||||
def delete_vim_auth(self, vim_id):
|
||||
"""Delete vim information
|
||||
|
||||
Delete vim key stored in file system
|
||||
"""
|
||||
LOG.debug(_('Attempting to delete key for vim id %s'), vim_id)
|
||||
key_file = os.path.join(CONF.vim_keys.openstack, vim_id)
|
||||
try:
|
||||
os.remove(key_file)
|
||||
LOG.debug(_('VIM key deleted successfully for vim %s'), vim_id)
|
||||
except OSError:
|
||||
LOG.warn(_('VIM key deletion unsuccessful for vim %s'), vim_id)
|
||||
|
||||
@log.log
|
||||
def encode_vim_auth(self, vim_id, auth):
|
||||
"""Encode VIM credentials
|
||||
|
||||
Store VIM auth using fernet key encryption
|
||||
"""
|
||||
fernet_key, fernet_obj = self.keystone.create_fernet_key()
|
||||
encoded_auth = fernet_obj.encrypt(auth['password'].encode('utf-8'))
|
||||
auth['password'] = encoded_auth
|
||||
key_file = os.path.join(CONF.vim_keys.openstack, vim_id)
|
||||
try:
|
||||
with open(key_file, 'w') as f:
|
||||
f.write(fernet_key.decode('utf-8'))
|
||||
LOG.debug(_('VIM auth successfully stored for vim %s'), vim_id)
|
||||
except IOError:
|
||||
raise nfvo.VimKeyNotFoundException(vim_id=vim_id)
|
91
tacker/nfvo/nfvo_plugin.py
Normal file
91
tacker/nfvo/nfvo_plugin.py
Normal file
@ -0,0 +1,91 @@
|
||||
# Copyright 2016 Brocade Communications System, Inc.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
#
|
||||
# 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 oslo_config import cfg
|
||||
|
||||
from tacker.common import driver_manager
|
||||
from tacker.common import log
|
||||
from tacker.common import utils
|
||||
from tacker.db.nfvo import nfvo_db
|
||||
from tacker.openstack.common import excutils
|
||||
from tacker.openstack.common import log as logging
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class NfvoPlugin(nfvo_db.NfvoPluginDb):
|
||||
"""NFVO reference plugin for NFVO extension
|
||||
|
||||
Implements the NFVO extension and defines public facing APIs for VIM
|
||||
operations. NFVO internally invokes the appropriate VIM driver in
|
||||
backend based on configured VIM types. Plugin also interacts with VNFM
|
||||
extension for providing the specified VIM information
|
||||
"""
|
||||
supported_extension_aliases = ['nfvo']
|
||||
|
||||
OPTS = [
|
||||
cfg.ListOpt(
|
||||
'vim_drivers', default=['openstack'],
|
||||
help=_('VIM driver for launching VNFs')),
|
||||
]
|
||||
cfg.CONF.register_opts(OPTS, 'nfvo_vim')
|
||||
|
||||
def __init__(self):
|
||||
super(NfvoPlugin, self).__init__()
|
||||
self._vim_drivers = driver_manager.DriverManager(
|
||||
'tacker.nfvo.vim.drivers',
|
||||
cfg.CONF.nfvo_vim.vim_drivers)
|
||||
|
||||
@log.log
|
||||
def create_vim(self, context, vim):
|
||||
LOG.debug(_('Create vim called with parameters %s'), vim)
|
||||
vim_obj = vim['vim']
|
||||
vim_type = vim_obj['type']
|
||||
vim_obj['id'] = str(uuid.uuid4())
|
||||
try:
|
||||
self._vim_drivers.invoke(vim_type, 'register_vim', vim_obj=vim_obj)
|
||||
res = super(NfvoPlugin, self).create_vim(context, vim_obj)
|
||||
return res
|
||||
except Exception:
|
||||
with excutils.save_and_reraise_exception():
|
||||
self._vim_drivers.invoke(vim_type, 'delete_vim_auth',
|
||||
vim_id=vim_obj['id'])
|
||||
|
||||
def _get_vim(self, context, vim_id):
|
||||
if not self.is_vim_still_in_use(context, vim_id):
|
||||
return self.get_vim(context, vim_id)
|
||||
|
||||
@log.log
|
||||
def update_vim(self, context, vim_id, vim):
|
||||
vim_obj = self._get_vim(context, vim_id)
|
||||
utils.deep_update(vim_obj, vim['vim'])
|
||||
vim_type = vim_obj['type']
|
||||
try:
|
||||
self._vim_drivers.invoke(vim_type, 'register_vim', vim_obj=vim_obj)
|
||||
return super(NfvoPlugin, self).update_vim(context, vim_id, vim_obj)
|
||||
except Exception:
|
||||
with excutils.save_and_reraise_exception():
|
||||
self._vim_drivers.invoke(vim_type, 'delete_vim_auth',
|
||||
vim_id=vim_obj['id'])
|
||||
|
||||
@log.log
|
||||
def delete_vim(self, context, vim_id):
|
||||
vim_obj = self._get_vim(context, vim_id)
|
||||
self._vim_drivers.invoke(vim_obj['type'], 'deregister_vim',
|
||||
vim_id=vim_id)
|
||||
super(NfvoPlugin, self).delete_vim(context, vim_id)
|
@ -19,11 +19,13 @@
|
||||
CORE = "CORE"
|
||||
DUMMY = "DUMMY"
|
||||
VNFM = "VNFM"
|
||||
NFVO = "NFVO"
|
||||
|
||||
COMMON_PREFIXES = {
|
||||
CORE: "",
|
||||
DUMMY: "/dummy_svc",
|
||||
VNFM: "",
|
||||
NFVO: ""
|
||||
}
|
||||
|
||||
# Service operation status constants
|
||||
|
33
tacker/tests/unit/base.py
Normal file
33
tacker/tests/unit/base.py
Normal file
@ -0,0 +1,33 @@
|
||||
# Copyright 2016 Brocade Communications System, Inc.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# 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 mock
|
||||
from oslo_config import cfg
|
||||
from oslo_config import fixture as config_fixture
|
||||
from oslotest import base
|
||||
|
||||
CONF = cfg.CONF
|
||||
|
||||
|
||||
class TestCase(base.BaseTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(TestCase, self).setUp()
|
||||
self.config_fixture = self.useFixture(config_fixture.Config(CONF))
|
||||
|
||||
def _mock(self, target, new=mock.DEFAULT):
|
||||
patcher = mock.patch(target, new)
|
||||
return patcher.start()
|
@ -14,10 +14,10 @@
|
||||
# under the License.
|
||||
|
||||
import fixtures
|
||||
import testtools
|
||||
|
||||
from tacker.db import api as db_api
|
||||
from tacker.db import model_base
|
||||
from tacker.tests.unit import base
|
||||
|
||||
|
||||
class SqlFixture(fixtures.Fixture):
|
||||
@ -42,7 +42,7 @@ class SqlFixture(fixtures.Fixture):
|
||||
self.addCleanup(clear_tables)
|
||||
|
||||
|
||||
class SqlTestCase(testtools.TestCase):
|
||||
class SqlTestCase(base.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(SqlTestCase, self).setUp()
|
||||
|
@ -47,6 +47,7 @@ def get_dummy_vnfd_obj():
|
||||
def get_dummy_vnf_obj():
|
||||
return {'vnf': {'description': 'dummy_vnf_description',
|
||||
'vnfd_id': u'eb094833-995e-49f0-a047-dfb56aaf7c4e',
|
||||
'vim_id': u'6261579e-d6f3-49ad-8bc3-a9cb974778ff',
|
||||
'tenant_id': u'ad7ebc56538745a08ef7c5e97f8bd437',
|
||||
'name': 'dummy_vnf',
|
||||
'attributes': {}}}
|
||||
@ -152,3 +153,10 @@ def get_dummy_device_obj_userdata_attr():
|
||||
'services': [], 'attributes': {u'param_values': userdata_params},
|
||||
'id': '18685f68-2b2a-4185-8566-74f54e548811',
|
||||
'description': u"Parameterized VNF descriptor"}
|
||||
|
||||
|
||||
def get_vim_auth_obj():
|
||||
return {'username': 'test_user', 'password': 'test_password',
|
||||
'project_id': None, 'project_name': 'test_project',
|
||||
'auth_url': 'http://localhost:5000/v3', 'user_domain_id':
|
||||
'default', 'project_domain_id': 'default'}
|
||||
|
@ -16,10 +16,10 @@
|
||||
import codecs
|
||||
import mock
|
||||
import os
|
||||
import testtools
|
||||
import yaml
|
||||
|
||||
from tacker import context
|
||||
from tacker.tests.unit import base
|
||||
from tacker.tests.unit.db import utils
|
||||
from tacker.vm.infra_drivers.heat import heat
|
||||
|
||||
@ -45,7 +45,7 @@ def _get_template(name):
|
||||
return f.read()
|
||||
|
||||
|
||||
class TestDeviceHeat(testtools.TestCase):
|
||||
class TestDeviceHeat(base.TestCase):
|
||||
hot_template = _get_template('hot_openwrt.yaml')
|
||||
hot_param_template = _get_template('hot_openwrt_params.yaml')
|
||||
hot_ipparam_template = _get_template('hot_openwrt_ipparams.yaml')
|
||||
@ -142,7 +142,8 @@ class TestDeviceHeat(testtools.TestCase):
|
||||
expected_result = '4a4c2d44-8a52-4895-9a75-9d1c76c3e738'
|
||||
expected_fields = self._get_expected_fields()
|
||||
result = self.heat_driver.create(plugin=None, context=self.context,
|
||||
device=device_obj)
|
||||
device=device_obj,
|
||||
auth_attr=utils.get_vim_auth_obj())
|
||||
self.heat_client.create.assert_called_once_with(expected_fields)
|
||||
self.assertEqual(expected_result, result)
|
||||
|
||||
@ -151,7 +152,8 @@ class TestDeviceHeat(testtools.TestCase):
|
||||
expected_result = '4a4c2d44-8a52-4895-9a75-9d1c76c3e738'
|
||||
expected_fields = self._get_expected_fields_user_data()
|
||||
result = self.heat_driver.create(plugin=None, context=self.context,
|
||||
device=device_obj)
|
||||
device=device_obj,
|
||||
auth_attr=utils.get_vim_auth_obj())
|
||||
self.heat_client.create.assert_called_once_with(expected_fields)
|
||||
self.assertEqual(expected_result, result)
|
||||
|
||||
@ -160,7 +162,8 @@ class TestDeviceHeat(testtools.TestCase):
|
||||
expected_result = '4a4c2d44-8a52-4895-9a75-9d1c76c3e738'
|
||||
expected_fields = self._get_expected_fields_ipaddr_data()
|
||||
result = self.heat_driver.create(plugin=None, context=self.context,
|
||||
device=device_obj)
|
||||
device=device_obj,
|
||||
auth_attr=utils.get_vim_auth_obj())
|
||||
self.heat_client.create.assert_called_once_with(expected_fields)
|
||||
self.assertEqual(expected_result, result)
|
||||
|
||||
@ -171,13 +174,15 @@ class TestDeviceHeat(testtools.TestCase):
|
||||
self.heat_driver.create_wait(plugin=None,
|
||||
context=self.context,
|
||||
device_dict=device_obj,
|
||||
device_id=device_id)
|
||||
device_id=device_id,
|
||||
auth_attr=utils.get_vim_auth_obj())
|
||||
self.assertEqual(device_obj, expected_result)
|
||||
|
||||
def test_delete(self):
|
||||
device_id = '4a4c2d44-8a52-4895-9a75-9d1c76c3e738'
|
||||
self.heat_driver.delete(plugin=None, context=self.context,
|
||||
device_id=device_id)
|
||||
device_id=device_id,
|
||||
auth_attr=utils.get_vim_auth_obj())
|
||||
self.heat_client.delete.assert_called_once_with(device_id)
|
||||
|
||||
def test_update(self):
|
||||
@ -185,9 +190,10 @@ class TestDeviceHeat(testtools.TestCase):
|
||||
device_config_obj = utils.get_dummy_device_update_config_attr()
|
||||
expected_device_update = self._get_expected_device_update_obj()
|
||||
device_id = '4a4c2d44-8a52-4895-9a75-9d1c76c3e738'
|
||||
self.heat_driver.update(None, self.context,
|
||||
device_id, device_obj,
|
||||
device_config_obj)
|
||||
self.heat_driver.update(plugin=None, context=self.context,
|
||||
device_id=device_id, device_dict=device_obj,
|
||||
device=device_config_obj,
|
||||
auth_attr=utils.get_vim_auth_obj())
|
||||
self.assertEqual(device_obj, expected_device_update)
|
||||
|
||||
def test_create_device_template_pre_tosca(self):
|
||||
@ -238,10 +244,10 @@ class TestDeviceHeat(testtools.TestCase):
|
||||
expected_result = '4a4c2d44-8a52-4895-9a75-9d1c76c3e738'
|
||||
expected_fields = self._get_expected_fields_tosca(hot_tpl_name)
|
||||
expected_device = self._get_expected_tosca_device(tosca_tpl_name,
|
||||
hot_tpl_name)
|
||||
hot_tpl_name)
|
||||
result = self.heat_driver.create(plugin=None, context=self.context,
|
||||
device=device)
|
||||
# self.heat_client.create.assert_called_once_with(expected_fields)
|
||||
device=device,
|
||||
auth_attr=utils.get_vim_auth_obj())
|
||||
actual_fields = self.heat_client.create.call_args[0][0]
|
||||
actual_fields["template"] = yaml.safe_load(actual_fields["template"])
|
||||
expected_fields["template"] = \
|
||||
@ -249,6 +255,7 @@ class TestDeviceHeat(testtools.TestCase):
|
||||
self.assertEqual(actual_fields, expected_fields)
|
||||
device["attributes"]["heat_template"] = yaml.safe_load(
|
||||
device["attributes"]["heat_template"])
|
||||
self.heat_client.create.assert_called_once_with(expected_fields)
|
||||
self.assertEqual(expected_result, result)
|
||||
self.assertEqual(device, expected_device)
|
||||
|
||||
|
0
tacker/tests/unit/vm/nfvo/__init__.py
Normal file
0
tacker/tests/unit/vm/nfvo/__init__.py
Normal file
0
tacker/tests/unit/vm/nfvo/drivers/__init__.py
Normal file
0
tacker/tests/unit/vm/nfvo/drivers/__init__.py
Normal file
0
tacker/tests/unit/vm/nfvo/drivers/vim/__init__.py
Normal file
0
tacker/tests/unit/vm/nfvo/drivers/vim/__init__.py
Normal file
117
tacker/tests/unit/vm/nfvo/drivers/vim/test_openstack_driver.py
Normal file
117
tacker/tests/unit/vm/nfvo/drivers/vim/test_openstack_driver.py
Normal file
@ -0,0 +1,117 @@
|
||||
# Copyright 2016 Brocade Communications System, Inc.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# 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 mock
|
||||
from oslo_config import cfg
|
||||
|
||||
from tacker.nfvo.drivers.vim import openstack_driver
|
||||
from tacker.tests.unit import base
|
||||
from tacker.tests.unit.db import utils
|
||||
|
||||
OPTS = [cfg.StrOpt('user_domain_id', default='default', help='User Domain Id'),
|
||||
cfg.StrOpt('project_domain_id', default='default', help='Project '
|
||||
'Domain Id')]
|
||||
cfg.CONF.register_opts(OPTS, 'keystone_authtoken')
|
||||
CONF = cfg.CONF
|
||||
|
||||
|
||||
class FakeKeystone(mock.Mock):
|
||||
pass
|
||||
|
||||
|
||||
class mock_dict(dict):
|
||||
def __getattr__(self, item):
|
||||
return self.get(item)
|
||||
|
||||
__setattr__ = dict.__setitem__
|
||||
__delattr__ = dict.__delitem__
|
||||
|
||||
|
||||
class TestOpenstack_Driver(base.TestCase):
|
||||
def setUp(self):
|
||||
super(TestOpenstack_Driver, self).setUp()
|
||||
self._mock_keystone()
|
||||
self.keystone.create_key_dir.return_value = 'test_keys'
|
||||
self.config_fixture.config(group='vim_keys', openstack='/tmp/')
|
||||
self.openstack_driver = openstack_driver.OpenStack_Driver()
|
||||
self.vim_obj = self.get_vim_obj()
|
||||
self.auth_obj = utils.get_vim_auth_obj()
|
||||
self.addCleanup(mock.patch.stopall)
|
||||
|
||||
def _mock_keystone(self):
|
||||
self.keystone = mock.Mock(wraps=FakeKeystone())
|
||||
fake_keystone = mock.Mock()
|
||||
fake_keystone.return_value = self.keystone
|
||||
self._mock(
|
||||
'tacker.vm.keystone.Keystone', fake_keystone)
|
||||
|
||||
def get_vim_obj(self):
|
||||
return {'id': '6261579e-d6f3-49ad-8bc3-a9cb974778ff', 'type':
|
||||
'openstack', 'auth_url': 'http://localhost:5000',
|
||||
'auth_cred': {'username': 'test_user', 'password':
|
||||
'test_password'}, 'name': 'VIM0',
|
||||
'vim_project': {'name': 'test_project'}}
|
||||
|
||||
def test_register_keystone_v3(self):
|
||||
regions = [mock_dict({'id': 'RegionOne'})]
|
||||
attrs = {'regions.list.return_value': regions}
|
||||
keystone_version = 'v3'
|
||||
mock_ks_client = mock.Mock(version=keystone_version, **attrs)
|
||||
self.keystone.get_version.return_value = keystone_version
|
||||
self._test_register_vim(self.vim_obj, mock_ks_client)
|
||||
mock_ks_client.regions.list.assert_called_once_with()
|
||||
self.keystone.initialize_client.assert_called_once_with(
|
||||
version=keystone_version, **self.auth_obj)
|
||||
|
||||
def test_register_keystone_v2(self):
|
||||
services_list = [mock_dict({'type': 'orchestration', 'id':
|
||||
'test_id'})]
|
||||
endpoints_regions = mock_dict({'region': 'RegionOne'})
|
||||
endpoints_list = [mock_dict({'service_id': 'test_id', 'regions':
|
||||
endpoints_regions})]
|
||||
attrs = {'endpoints.list.return_value': endpoints_list,
|
||||
'services.list.return_value': services_list}
|
||||
keystone_version = 'v2.0'
|
||||
mock_ks_client = mock.Mock(version='v2.0', **attrs)
|
||||
self.keystone.get_version.return_value = keystone_version
|
||||
auth_obj = {'tenant_name': 'test_project', 'username': 'test_user',
|
||||
'password': 'test_password', 'auth_url':
|
||||
'http://localhost:5000/v2.0', 'tenant_id': None}
|
||||
self._test_register_vim(self.vim_obj, mock_ks_client)
|
||||
self.keystone.initialize_client.assert_called_once_with(
|
||||
version=keystone_version, **auth_obj)
|
||||
|
||||
def _test_register_vim(self, vim_obj, mock_ks_client):
|
||||
self.keystone.initialize_client.return_value = mock_ks_client
|
||||
fernet_attrs = {'encrypt.return_value': 'encrypted_password'}
|
||||
mock_fernet_obj = mock.Mock(**fernet_attrs)
|
||||
mock_fernet_key = 'test_fernet_key'
|
||||
self.keystone.create_fernet_key.return_value = (mock_fernet_key,
|
||||
mock_fernet_obj)
|
||||
file_mock = mock.mock_open()
|
||||
with mock.patch('six.moves.builtins.open', file_mock, create=True):
|
||||
self.openstack_driver.register_vim(vim_obj)
|
||||
mock_fernet_obj.encrypt.assert_called_once_with(mock.ANY)
|
||||
file_mock().write.assert_called_once_with('test_fernet_key')
|
||||
|
||||
@mock.patch('tacker.nfvo.drivers.vim.openstack_driver.os.remove')
|
||||
@mock.patch('tacker.nfvo.drivers.vim.openstack_driver.os.path'
|
||||
'.join')
|
||||
def test_deregister_vim(self, mock_os_path, mock_os_remove):
|
||||
vim_id = 'my_id'
|
||||
file_path = CONF.vim_keys.openstack + '/' + vim_id
|
||||
mock_os_path.return_value = file_path
|
||||
self.openstack_driver.deregister_vim(vim_id)
|
||||
mock_os_remove.assert_called_once_with(file_path)
|
115
tacker/tests/unit/vm/nfvo/test_nfvo_plugin.py
Normal file
115
tacker/tests/unit/vm/nfvo/test_nfvo_plugin.py
Normal file
@ -0,0 +1,115 @@
|
||||
# Copyright 2016 Brocade Communications System, Inc.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# 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
|
||||
|
||||
import mock
|
||||
|
||||
from tacker import context
|
||||
from tacker.db.nfvo import nfvo_db
|
||||
from tacker.nfvo import nfvo_plugin
|
||||
from tacker.tests.unit.db import base as db_base
|
||||
|
||||
|
||||
class FakeDriverManager(mock.Mock):
|
||||
def invoke(self, *args, **kwargs):
|
||||
if 'create' in args:
|
||||
return str(uuid.uuid4())
|
||||
|
||||
|
||||
class TestNfvoPlugin(db_base.SqlTestCase):
|
||||
def setUp(self):
|
||||
super(TestNfvoPlugin, self).setUp()
|
||||
self.addCleanup(mock.patch.stopall)
|
||||
self.context = context.get_admin_context()
|
||||
self._mock_driver_manager()
|
||||
self.nfvo_plugin = nfvo_plugin.NfvoPlugin()
|
||||
|
||||
def _mock_driver_manager(self):
|
||||
self._driver_manager = mock.Mock(wraps=FakeDriverManager())
|
||||
self._driver_manager.__contains__ = mock.Mock(
|
||||
return_value=True)
|
||||
fake_driver_manager = mock.Mock()
|
||||
fake_driver_manager.return_value = self._driver_manager
|
||||
self._mock(
|
||||
'tacker.common.driver_manager.DriverManager', fake_driver_manager)
|
||||
|
||||
def _insert_dummy_vim(self):
|
||||
session = self.context.session
|
||||
vim_db = nfvo_db.Vim(
|
||||
id='6261579e-d6f3-49ad-8bc3-a9cb974778ff',
|
||||
tenant_id='ad7ebc56538745a08ef7c5e97f8bd437',
|
||||
name='fake_vim',
|
||||
description='fake_vim_description',
|
||||
type='openstack',
|
||||
placement_attr={'regions': ['RegionOne']})
|
||||
vim_auth_db = nfvo_db.VimAuth(
|
||||
vim_id='6261579e-d6f3-49ad-8bc3-a9cb974778ff',
|
||||
password='encrypted_pw',
|
||||
auth_url='http://localhost:5000',
|
||||
vim_project={'name': 'test_project'},
|
||||
auth_cred={'username': 'test_user', 'user_domain_id': 'default',
|
||||
'project_domain_d': 'default'})
|
||||
session.add(vim_db)
|
||||
session.add(vim_auth_db)
|
||||
session.flush()
|
||||
|
||||
def test_create_vim(self):
|
||||
vim_dict = {'vim': {'type': 'openstack', 'auth_url':
|
||||
'http://localhost:5000', 'vim_project': {'name':
|
||||
'test_project'}, 'auth_cred': {'username': 'test_user',
|
||||
'password':
|
||||
'test_password'},
|
||||
'name': 'VIM0'}}
|
||||
vim_type = 'openstack'
|
||||
res = self.nfvo_plugin.create_vim(self.context, vim_dict)
|
||||
self._driver_manager.invoke.assert_called_once_with(vim_type,
|
||||
'register_vim',
|
||||
vim_obj=vim_dict[
|
||||
'vim'])
|
||||
self.assertIsNotNone(res)
|
||||
self.assertIn('id', res)
|
||||
self.assertIn('placement_attr', res)
|
||||
|
||||
def test_delete_vim(self):
|
||||
self._insert_dummy_vim()
|
||||
vim_type = 'openstack'
|
||||
vim_id = '6261579e-d6f3-49ad-8bc3-a9cb974778ff'
|
||||
self.nfvo_plugin.delete_vim(self.context, vim_id)
|
||||
self._driver_manager.invoke.assert_called_once_with(vim_type,
|
||||
'deregister_vim',
|
||||
vim_id=vim_id)
|
||||
|
||||
def test_update_vim(self):
|
||||
vim_dict = {'vim': {'id': '6261579e-d6f3-49ad-8bc3-a9cb974778ff',
|
||||
'vim_project': {'name': 'new_project'},
|
||||
'auth_cred': {'username': 'new_user',
|
||||
'password': 'new_password'}}}
|
||||
vim_type = 'openstack'
|
||||
vim_auth_username = vim_dict['vim']['auth_cred']['username']
|
||||
vim_auth_password = vim_dict['vim']['auth_cred']['password']
|
||||
vim_project = vim_dict['vim']['vim_project']
|
||||
self._insert_dummy_vim()
|
||||
res = self.nfvo_plugin.update_vim(self.context, vim_dict['vim']['id'],
|
||||
vim_dict)
|
||||
self._driver_manager.invoke.assert_called_once_with(vim_type,
|
||||
'register_vim',
|
||||
vim_obj=mock.ANY)
|
||||
self.assertIsNotNone(res)
|
||||
self.assertIn('id', res)
|
||||
self.assertIn('placement_attr', res)
|
||||
self.assertEqual(vim_project, res['vim_project'])
|
||||
self.assertEqual(vim_auth_username, res['auth_cred']['username'])
|
||||
self.assertEqual(vim_auth_password, res['auth_cred']['password'])
|
@ -18,6 +18,7 @@ import uuid
|
||||
import mock
|
||||
|
||||
from tacker import context
|
||||
from tacker.db.nfvo import nfvo_db
|
||||
from tacker.db.vm import vm_db
|
||||
from tacker.extensions import vnfm
|
||||
from tacker.tests.unit.db import base as db_base
|
||||
@ -39,14 +40,21 @@ class FakeGreenPool(mock.Mock):
|
||||
pass
|
||||
|
||||
|
||||
class FakeVimClient(mock.Mock):
|
||||
pass
|
||||
|
||||
|
||||
class TestVNFMPlugin(db_base.SqlTestCase):
|
||||
def setUp(self):
|
||||
super(TestVNFMPlugin, self).setUp()
|
||||
self.addCleanup(mock.patch.stopall)
|
||||
self.context = context.get_admin_context()
|
||||
self._mock_vim_client()
|
||||
self._stub_get_vim()
|
||||
self._mock_device_manager()
|
||||
self._mock_vnf_monitor()
|
||||
self._mock_green_pool()
|
||||
self._insert_dummy_vim()
|
||||
self.vnfm_plugin = plugin.VNFMPlugin()
|
||||
|
||||
def _mock_device_manager(self):
|
||||
@ -58,12 +66,20 @@ class TestVNFMPlugin(db_base.SqlTestCase):
|
||||
self._mock(
|
||||
'tacker.common.driver_manager.DriverManager', fake_device_manager)
|
||||
|
||||
def _mock_vnf_monitor(self):
|
||||
self._vnf_monitor = mock.Mock(wraps=FakeVNFMonitor())
|
||||
fake_vnf_monitor = mock.Mock()
|
||||
fake_vnf_monitor.return_value = self._vnf_monitor
|
||||
def _mock_vim_client(self):
|
||||
self.vim_client = mock.Mock(wraps=FakeVimClient())
|
||||
fake_vim_client = mock.Mock()
|
||||
fake_vim_client.return_value = self.vim_client
|
||||
self._mock(
|
||||
'tacker.vm.monitor.VNFMonitor', fake_vnf_monitor)
|
||||
'tacker.vm.vim_client.VimClient', fake_vim_client)
|
||||
|
||||
def _stub_get_vim(self):
|
||||
vim_obj = {'vim_id': '6261579e-d6f3-49ad-8bc3-a9cb974778ff',
|
||||
'vim_name': 'fake_vim', 'vim_auth':
|
||||
{'auth_url': 'http://localhost:5000', 'password':
|
||||
'test_pw', 'username': 'test_user', 'project_name':
|
||||
'test_project'}}
|
||||
self.vim_client.get_vim.return_value = vim_obj
|
||||
|
||||
def _mock_green_pool(self):
|
||||
self._pool = mock.Mock(wraps=FakeGreenPool())
|
||||
@ -72,9 +88,12 @@ class TestVNFMPlugin(db_base.SqlTestCase):
|
||||
self._mock(
|
||||
'eventlet.GreenPool', fake_green_pool)
|
||||
|
||||
def _mock(self, target, new=mock.DEFAULT):
|
||||
patcher = mock.patch(target, new)
|
||||
return patcher.start()
|
||||
def _mock_vnf_monitor(self):
|
||||
self._vnf_monitor = mock.Mock(wraps=FakeVNFMonitor())
|
||||
fake_vnf_monitor = mock.Mock()
|
||||
fake_vnf_monitor.return_value = self._vnf_monitor
|
||||
self._mock(
|
||||
'tacker.vm.monitor.VNFMonitor', fake_vnf_monitor)
|
||||
|
||||
def _insert_dummy_device_template(self):
|
||||
session = self.context.session
|
||||
@ -98,11 +117,33 @@ class TestVNFMPlugin(db_base.SqlTestCase):
|
||||
description='fake_device_description',
|
||||
instance_id='da85ea1a-4ec4-4201-bbb2-8d9249eca7ec',
|
||||
template_id='eb094833-995e-49f0-a047-dfb56aaf7c4e',
|
||||
vim_id='6261579e-d6f3-49ad-8bc3-a9cb974778ff',
|
||||
placement_attr={'region': 'RegionOne'},
|
||||
status='ACTIVE')
|
||||
session.add(device_db)
|
||||
session.flush()
|
||||
return device_db
|
||||
|
||||
def _insert_dummy_vim(self):
|
||||
session = self.context.session
|
||||
vim_db = nfvo_db.Vim(
|
||||
id='6261579e-d6f3-49ad-8bc3-a9cb974778ff',
|
||||
tenant_id='ad7ebc56538745a08ef7c5e97f8bd437',
|
||||
name='fake_vim',
|
||||
description='fake_vim_description',
|
||||
type='openstack',
|
||||
placement_attr={'regions': ['RegionOne']})
|
||||
vim_auth_db = nfvo_db.VimAuth(
|
||||
vim_id='6261579e-d6f3-49ad-8bc3-a9cb974778ff',
|
||||
password='encrypted_pw',
|
||||
auth_url='http://localhost:5000',
|
||||
vim_project={'name': 'test_project'},
|
||||
auth_cred={'username': 'test_user', 'user_domain_id': 'default',
|
||||
'project_domain_d': 'default'})
|
||||
session.add(vim_db)
|
||||
session.add(vim_auth_db)
|
||||
session.flush()
|
||||
|
||||
def test_create_vnfd(self):
|
||||
vnfd_obj = utils.get_dummy_vnfd_obj()
|
||||
result = self.vnfm_plugin.create_vnfd(self.context, vnfd_obj)
|
||||
@ -144,7 +185,8 @@ class TestVNFMPlugin(db_base.SqlTestCase):
|
||||
self._device_manager.invoke.assert_called_with(mock.ANY, mock.ANY,
|
||||
plugin=mock.ANY,
|
||||
context=mock.ANY,
|
||||
device=mock.ANY)
|
||||
device=mock.ANY,
|
||||
auth_attr=mock.ANY)
|
||||
self._pool.spawn_n.assert_called_once_with(mock.ANY)
|
||||
|
||||
def test_delete_vnf(self):
|
||||
@ -155,10 +197,12 @@ class TestVNFMPlugin(db_base.SqlTestCase):
|
||||
self._device_manager.invoke.assert_called_with(mock.ANY, mock.ANY,
|
||||
plugin=mock.ANY,
|
||||
context=mock.ANY,
|
||||
device_id=mock.ANY)
|
||||
device_id=mock.ANY,
|
||||
auth_attr=mock.ANY,
|
||||
region_name=mock.ANY)
|
||||
self._vnf_monitor.delete_hosting_vnf.assert_called_with(mock.ANY)
|
||||
self._pool.spawn_n.assert_called_once_with(mock.ANY, mock.ANY,
|
||||
mock.ANY)
|
||||
mock.ANY, mock.ANY)
|
||||
|
||||
def test_update_vnf(self):
|
||||
self._insert_dummy_device_template()
|
||||
@ -173,4 +217,4 @@ class TestVNFMPlugin(db_base.SqlTestCase):
|
||||
self.assertIn('attributes', result)
|
||||
self.assertIn('mgmt_url', result)
|
||||
self._pool.spawn_n.assert_called_once_with(mock.ANY, mock.ANY,
|
||||
mock.ANY)
|
||||
mock.ANY, mock.ANY)
|
||||
|
@ -174,11 +174,12 @@ class DeviceHeat(abstract_driver.DeviceAbstractDriver):
|
||||
@log.log
|
||||
def _process_vdu_network_interfaces(self, vdu_id, vdu_dict, properties,
|
||||
template_dict):
|
||||
|
||||
def make_port_dict():
|
||||
port_dict = {
|
||||
'type': 'OS::Neutron::Port',
|
||||
'properties': {
|
||||
'port_security_enabled': False
|
||||
'port_security_enabled': False
|
||||
}
|
||||
}
|
||||
port_dict['properties'].setdefault('fixed_ips', [])
|
||||
@ -229,9 +230,8 @@ class DeviceHeat(abstract_driver.DeviceAbstractDriver):
|
||||
networks_list.append(dict(network_param))
|
||||
|
||||
@log.log
|
||||
def create(self, plugin, context, device):
|
||||
def create(self, plugin, context, device, auth_attr):
|
||||
LOG.debug(_('device %s'), device)
|
||||
heatclient_ = HeatClient(context)
|
||||
attributes = device['device_template']['attributes'].copy()
|
||||
vnfd_yaml = attributes.pop('vnfd', None)
|
||||
fields = dict((key, attributes.pop(key)) for key
|
||||
@ -251,6 +251,9 @@ class DeviceHeat(abstract_driver.DeviceAbstractDriver):
|
||||
fields.setdefault(key, {}).update(
|
||||
jsonutils.loads(dev_attrs.pop(key)))
|
||||
|
||||
region_name = device.get('placement_attr', {}).get('region_name', None)
|
||||
heatclient_ = HeatClient(auth_attr, region_name)
|
||||
|
||||
LOG.debug('vnfd_yaml %s', vnfd_yaml)
|
||||
if vnfd_yaml is not None:
|
||||
vnfd_dict = yamlparser.simple_ordered_parse(vnfd_yaml)
|
||||
@ -381,8 +384,10 @@ class DeviceHeat(abstract_driver.DeviceAbstractDriver):
|
||||
stack = heatclient_.create(fields)
|
||||
return stack['stack']['id']
|
||||
|
||||
def create_wait(self, plugin, context, device_dict, device_id):
|
||||
heatclient_ = HeatClient(context)
|
||||
def create_wait(self, plugin, context, device_dict, device_id, auth_attr):
|
||||
region_name = device_dict.get('placement_attr', {}).get(
|
||||
'region_name', None)
|
||||
heatclient_ = HeatClient(auth_attr, region_name)
|
||||
|
||||
stack = heatclient_.get(device_id)
|
||||
status = stack.stack_status
|
||||
@ -422,9 +427,11 @@ class DeviceHeat(abstract_driver.DeviceAbstractDriver):
|
||||
device_dict['mgmt_url'] = jsonutils.dumps(mgmt_ips)
|
||||
|
||||
@log.log
|
||||
def update(self, plugin, context, device_id, device_dict, device):
|
||||
# checking if the stack exists at the moment
|
||||
heatclient_ = HeatClient(context)
|
||||
def update(self, plugin, context, device_id, device_dict, device,
|
||||
auth_attr):
|
||||
region_name = device_dict.get('placement_attr', {}).get(
|
||||
'region_name', None)
|
||||
heatclient_ = HeatClient(auth_attr, region_name)
|
||||
heatclient_.get(device_id)
|
||||
|
||||
# update config attribute
|
||||
@ -461,18 +468,20 @@ class DeviceHeat(abstract_driver.DeviceAbstractDriver):
|
||||
new_yaml = yaml.dump(config_dict)
|
||||
device_dict.setdefault('attributes', {})['config'] = new_yaml
|
||||
|
||||
def update_wait(self, plugin, context, device_id):
|
||||
def update_wait(self, plugin, context, device_id, auth_attr,
|
||||
region_name=None):
|
||||
# do nothing but checking if the stack exists at the moment
|
||||
heatclient_ = HeatClient(context)
|
||||
heatclient_ = HeatClient(auth_attr, region_name)
|
||||
heatclient_.get(device_id)
|
||||
|
||||
def delete(self, plugin, context, device_id):
|
||||
heatclient_ = HeatClient(context)
|
||||
def delete(self, plugin, context, device_id, auth_attr, region_name=None):
|
||||
heatclient_ = HeatClient(auth_attr, region_name)
|
||||
heatclient_.delete(device_id)
|
||||
|
||||
@log.log
|
||||
def delete_wait(self, plugin, context, device_id):
|
||||
heatclient_ = HeatClient(context)
|
||||
def delete_wait(self, plugin, context, device_id, auth_attr,
|
||||
region_name=None):
|
||||
heatclient_ = HeatClient(auth_attr, region_name)
|
||||
|
||||
stack = heatclient_.get(device_id)
|
||||
status = stack.stack_status
|
||||
@ -513,9 +522,11 @@ class DeviceHeat(abstract_driver.DeviceAbstractDriver):
|
||||
|
||||
|
||||
class HeatClient(object):
|
||||
def __init__(self, context, password=None):
|
||||
def __init__(self, auth_attr, region_name=None):
|
||||
# context, password are unused
|
||||
self.stacks = clients.OpenstackClients().heat.stacks
|
||||
self.heat = clients.OpenstackClients(auth_attr, region_name).heat
|
||||
self.stacks = self.heat.stacks
|
||||
self.resource_types = self.heat.resource_types
|
||||
|
||||
def create(self, fields):
|
||||
fields = fields.copy()
|
||||
|
86
tacker/vm/keystone.py
Normal file
86
tacker/vm/keystone.py
Normal file
@ -0,0 +1,86 @@
|
||||
# Copyright 2016 Brocade Communications System, Inc.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
#
|
||||
# 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
|
||||
|
||||
from cryptography.fernet import Fernet
|
||||
from keystoneclient.auth import identity
|
||||
from keystoneclient import client
|
||||
from keystoneclient import exceptions
|
||||
from keystoneclient import session
|
||||
from oslo_config import cfg
|
||||
|
||||
from tacker.openstack.common import log as logging
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
CONF = cfg.CONF
|
||||
|
||||
|
||||
class Keystone(object):
|
||||
"""Keystone module for OpenStack VIM
|
||||
|
||||
Handles identity operations for a given OpenStack
|
||||
instance such as version, session and client
|
||||
"""
|
||||
|
||||
def get_version(self, base_url=None):
|
||||
try:
|
||||
keystone_client = client.Client(auth_url=base_url)
|
||||
except exceptions.ConnectionRefused:
|
||||
raise
|
||||
return keystone_client.version
|
||||
|
||||
def get_session(self, auth_plugin):
|
||||
ses = session.Session(auth=auth_plugin)
|
||||
return ses
|
||||
|
||||
def get_endpoint(self, ses, service_type, region_name=None):
|
||||
return ses.get_endpoint(service_type, region_name)
|
||||
|
||||
def initialize_client(self, version, **kwargs):
|
||||
if version == 'v2.0':
|
||||
from keystoneclient.v2_0 import client
|
||||
auth_plugin = identity.v2.Password(**kwargs)
|
||||
else:
|
||||
from keystoneclient.v3 import client
|
||||
auth_plugin = identity.v3.Password(**kwargs)
|
||||
ses = self.get_session(auth_plugin=auth_plugin)
|
||||
try:
|
||||
cli = client.Client(session=ses)
|
||||
return cli
|
||||
except (exceptions.AuthorizationFailure,
|
||||
exceptions.Unauthorized):
|
||||
LOG.warn(_("Authorization failed for user"))
|
||||
raise
|
||||
|
||||
@staticmethod
|
||||
def create_key_dir(path):
|
||||
if not os.access(path, os.F_OK):
|
||||
LOG.info(_(
|
||||
'[fernet_tokens] key_repository does not appear to exist; '
|
||||
'attempting to create it'))
|
||||
try:
|
||||
os.makedirs(path, 0o700)
|
||||
except OSError:
|
||||
LOG.error(_(
|
||||
'Failed to create [fernet_tokens] key_repository: either'
|
||||
'it already exists or you don\'t have sufficient'
|
||||
'permissions to create it'))
|
||||
|
||||
def create_fernet_key(self):
|
||||
fernet_key = Fernet.generate_key()
|
||||
fernet_obj = Fernet(fernet_key)
|
||||
return fernet_key, fernet_obj
|
@ -239,7 +239,7 @@ class ActionRespawn(ActionPolicy):
|
||||
@ActionPolicy.register('respawn', 'heat')
|
||||
class ActionRespawnHeat(ActionPolicy):
|
||||
@classmethod
|
||||
def execute_action(cls, plugin, device_dict):
|
||||
def execute_action(cls, plugin, device_dict, auth_attr):
|
||||
device_id = device_dict['id']
|
||||
LOG.error(_('device %s dead'), device_id)
|
||||
if plugin._mark_device_dead(device_dict['id']):
|
||||
@ -257,25 +257,19 @@ class ActionRespawnHeat(ActionPolicy):
|
||||
attributes = device_dict['attributes'].copy()
|
||||
attributes['dead_device_id'] = device_id
|
||||
new_device = {'id': new_device_id, 'attributes': attributes}
|
||||
for key in ('tenant_id', 'template_id', 'name'):
|
||||
for key in ('tenant_id', 'template_id', 'name', 'vim_id',
|
||||
'placement_attr'):
|
||||
new_device[key] = device_dict[key]
|
||||
LOG.debug(_('new_device %s'), new_device)
|
||||
|
||||
placement_attr = device_dict.get('placement_attr', {})
|
||||
region_name = placement_attr.get('region_name', None)
|
||||
# kill heat stack
|
||||
heatclient = heat.HeatClient(None)
|
||||
heatclient = heat.HeatClient(auth_attr=auth_attr,
|
||||
region_name=region_name)
|
||||
heatclient.delete(device_dict['instance_id'])
|
||||
|
||||
# keystone v2.0 specific
|
||||
authtoken = CONF.keystone_authtoken
|
||||
token = clients.OpenstackClients().auth_token
|
||||
|
||||
# TODO(anyone) set the current request ctxt instead of admin ctxt
|
||||
context = t_context.get_admin_context()
|
||||
context.tenant_name = authtoken.project_name
|
||||
context.user_name = authtoken.username
|
||||
context.auth_token = token['id']
|
||||
context.tenant_id = token['tenant_id']
|
||||
context.user_id = token['user_id']
|
||||
|
||||
new_device_dict = plugin.create_device_sync(
|
||||
context, {'device': new_device})
|
||||
LOG.info(_('respawned new device %s'), new_device_dict['id'])
|
||||
@ -295,7 +289,7 @@ class ActionRespawnHeat(ActionPolicy):
|
||||
new_device_dict.setdefault('attributes', {})['config'] = config
|
||||
|
||||
plugin.config_device(context, new_device_dict)
|
||||
plugin.add_device_to_monitor(new_device_dict)
|
||||
plugin.add_device_to_monitor(new_device_dict, auth_attr)
|
||||
|
||||
|
||||
@ActionPolicy.register('log')
|
||||
|
@ -28,14 +28,16 @@ from tacker.api.v1 import attributes
|
||||
from tacker.common import driver_manager
|
||||
from tacker.db.vm import vm_db
|
||||
from tacker.extensions import vnfm
|
||||
from tacker.i18n import _LE
|
||||
from tacker.openstack.common import excutils
|
||||
from tacker.openstack.common.gettextutils import _LE
|
||||
from tacker.openstack.common import log as logging
|
||||
from tacker.plugins.common import constants
|
||||
from tacker.vm.mgmt_drivers import constants as mgmt_constants
|
||||
from tacker.vm import monitor
|
||||
from tacker.vm import vim_client
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
CONF = cfg.CONF
|
||||
|
||||
|
||||
class VNFMMgmtMixin(object):
|
||||
@ -46,7 +48,7 @@ class VNFMMgmtMixin(object):
|
||||
'Hosting Device/logical service '
|
||||
'instance tacker plugin will use')),
|
||||
cfg.IntOpt('boot_wait', default=30,
|
||||
help=_('Time interval to wait for VM to boot')),
|
||||
help=_('Time interval to wait for VM to boot'))
|
||||
]
|
||||
cfg.CONF.register_opts(OPTS, 'tacker')
|
||||
|
||||
@ -115,6 +117,7 @@ class VNFMPlugin(vm_db.VNFMPluginDb, VNFMMgmtMixin):
|
||||
super(VNFMPlugin, self).__init__()
|
||||
self._pool = eventlet.GreenPool()
|
||||
self.boot_wait = cfg.CONF.tacker.boot_wait
|
||||
self.vim_client = vim_client.VimClient()
|
||||
self._device_manager = driver_manager.DriverManager(
|
||||
'tacker.tacker.device.drivers',
|
||||
cfg.CONF.tacker.infra_driver)
|
||||
@ -160,7 +163,7 @@ class VNFMPlugin(vm_db.VNFMPluginDb, VNFMMgmtMixin):
|
||||
|
||||
###########################################################################
|
||||
# hosting device
|
||||
def add_device_to_monitor(self, device_dict):
|
||||
def add_device_to_monitor(self, device_dict, vim_auth):
|
||||
dev_attrs = device_dict['attributes']
|
||||
mgmt_url = device_dict['mgmt_url']
|
||||
if 'monitoring_policy' in dev_attrs and mgmt_url:
|
||||
@ -168,7 +171,8 @@ class VNFMPlugin(vm_db.VNFMPluginDb, VNFMMgmtMixin):
|
||||
action_cls = monitor.ActionPolicy.get_policy(action,
|
||||
device_dict)
|
||||
if action_cls:
|
||||
action_cls.execute_action(self, hosting_vnf['device'])
|
||||
action_cls.execute_action(self, hosting_vnf['device'],
|
||||
vim_auth)
|
||||
|
||||
hosting_vnf = self._vnf_monitor.to_hosting_vnf(
|
||||
device_dict, action_cb)
|
||||
@ -189,7 +193,7 @@ class VNFMPlugin(vm_db.VNFMPluginDb, VNFMMgmtMixin):
|
||||
}
|
||||
self.update_device(context, device_id, update)
|
||||
|
||||
def _create_device_wait(self, context, device_dict):
|
||||
def _create_device_wait(self, context, device_dict, auth_attr):
|
||||
driver_name = self._infra_driver_name(device_dict)
|
||||
device_id = device_dict['id']
|
||||
instance_id = self._instance_id(device_dict)
|
||||
@ -198,7 +202,8 @@ class VNFMPlugin(vm_db.VNFMPluginDb, VNFMMgmtMixin):
|
||||
try:
|
||||
self._device_manager.invoke(
|
||||
driver_name, 'create_wait', plugin=self, context=context,
|
||||
device_dict=device_dict, device_id=instance_id)
|
||||
device_dict=device_dict, device_id=instance_id,
|
||||
auth_attr=auth_attr)
|
||||
except vnfm.DeviceCreateWaitFailed:
|
||||
LOG.error(_LE("VNF Create failed for vnf_id %s"), device_id)
|
||||
create_failed = True
|
||||
@ -233,7 +238,16 @@ class VNFMPlugin(vm_db.VNFMPluginDb, VNFMMgmtMixin):
|
||||
device_dict['status'] = new_status
|
||||
self._create_device_status(context, device_id, new_status)
|
||||
|
||||
def _create_device(self, context, device):
|
||||
def get_vim(self, context, device):
|
||||
region_name = device.setdefault('placement_attr', {}).get(
|
||||
'region_name', None)
|
||||
vim_res = self.vim_client.get_vim(context, device['vim_id'],
|
||||
region_name)
|
||||
device['placement_attr']['vim_name'] = vim_res['vim_name']
|
||||
device['vim_id'] = vim_res['vim_id']
|
||||
return vim_res['vim_auth']
|
||||
|
||||
def _create_device(self, context, device, vim_auth):
|
||||
device_dict = self._create_device_pre(context, device)
|
||||
device_id = device_dict['id']
|
||||
driver_name = self._infra_driver_name(device_dict)
|
||||
@ -242,7 +256,7 @@ class VNFMPlugin(vm_db.VNFMPluginDb, VNFMMgmtMixin):
|
||||
try:
|
||||
instance_id = self._device_manager.invoke(
|
||||
driver_name, 'create', plugin=self,
|
||||
context=context, device=device_dict)
|
||||
context=context, device=device_dict, auth_attr=vim_auth)
|
||||
except Exception:
|
||||
with excutils.save_and_reraise_exception():
|
||||
self.delete_device(context, device_id)
|
||||
@ -251,16 +265,17 @@ class VNFMPlugin(vm_db.VNFMPluginDb, VNFMMgmtMixin):
|
||||
self._create_device_post(context, device_id, None, None,
|
||||
device_dict)
|
||||
return
|
||||
|
||||
device_dict['instance_id'] = instance_id
|
||||
return device_dict
|
||||
|
||||
def create_device(self, context, device):
|
||||
device_dict = self._create_device(context, device)
|
||||
device_info = device['device']
|
||||
vim_auth = self.get_vim(context, device_info)
|
||||
device_dict = self._create_device(context, device_info, vim_auth)
|
||||
|
||||
def create_device_wait():
|
||||
self._create_device_wait(context, device_dict)
|
||||
self.add_device_to_monitor(device_dict)
|
||||
self._create_device_wait(context, device_dict, vim_auth)
|
||||
self.add_device_to_monitor(device_dict, vim_auth)
|
||||
self.config_device(context, device_dict)
|
||||
self.spawn_n(create_device_wait)
|
||||
return device_dict
|
||||
@ -268,11 +283,13 @@ class VNFMPlugin(vm_db.VNFMPluginDb, VNFMMgmtMixin):
|
||||
# not for wsgi, but for service to create hosting device
|
||||
# the device is NOT added to monitor.
|
||||
def create_device_sync(self, context, device):
|
||||
device_dict = self._create_device(context, device)
|
||||
self._create_device_wait(context, device_dict)
|
||||
device_info = device['device']
|
||||
vim_auth = self.get_vim(context, device_info)
|
||||
device_dict = self._create_device(context, device_info, vim_auth)
|
||||
self._create_device_wait(context, device_dict, vim_auth)
|
||||
return device_dict
|
||||
|
||||
def _update_device_wait(self, context, device_dict):
|
||||
def _update_device_wait(self, context, device_dict, vim_auth):
|
||||
driver_name = self._infra_driver_name(device_dict)
|
||||
instance_id = self._instance_id(device_dict)
|
||||
kwargs = {
|
||||
@ -280,10 +297,14 @@ class VNFMPlugin(vm_db.VNFMPluginDb, VNFMMgmtMixin):
|
||||
mgmt_constants.KEY_KWARGS: {'device': device_dict},
|
||||
}
|
||||
new_status = constants.ACTIVE
|
||||
placement_attr = device_dict['placement_attr']
|
||||
region_name = placement_attr.get('region_name', None)
|
||||
|
||||
try:
|
||||
self._device_manager.invoke(
|
||||
driver_name, 'update_wait', plugin=self,
|
||||
context=context, device_id=instance_id)
|
||||
context=context, device_id=instance_id, auth_attr=vim_auth,
|
||||
region_name=region_name)
|
||||
self.mgmt_call(context, device_dict, kwargs)
|
||||
except Exception:
|
||||
LOG.exception(_('_update_device_wait'))
|
||||
@ -296,6 +317,7 @@ class VNFMPlugin(vm_db.VNFMPluginDb, VNFMMgmtMixin):
|
||||
|
||||
def update_device(self, context, device_id, device):
|
||||
device_dict = self._update_device_pre(context, device_id)
|
||||
vim_auth = self.get_vim(context, device_dict)
|
||||
driver_name = self._infra_driver_name(device_dict)
|
||||
instance_id = self._instance_id(device_dict)
|
||||
|
||||
@ -303,25 +325,28 @@ class VNFMPlugin(vm_db.VNFMPluginDb, VNFMMgmtMixin):
|
||||
self.mgmt_update_pre(context, device_dict)
|
||||
self._device_manager.invoke(
|
||||
driver_name, 'update', plugin=self, context=context,
|
||||
device_id=instance_id, device_dict=device_dict, device=device)
|
||||
device_id=instance_id, device_dict=device_dict,
|
||||
device=device, auth_attr=vim_auth)
|
||||
except Exception:
|
||||
with excutils.save_and_reraise_exception():
|
||||
device_dict['status'] = constants.ERROR
|
||||
self.mgmt_update_post(context, device_dict)
|
||||
self._update_device_post(context, device_id, constants.ERROR)
|
||||
|
||||
self.spawn_n(self._update_device_wait, context, device_dict)
|
||||
self.spawn_n(self._update_device_wait, context, device_dict, vim_auth)
|
||||
return device_dict
|
||||
|
||||
def _delete_device_wait(self, context, device_dict):
|
||||
def _delete_device_wait(self, context, device_dict, auth_attr):
|
||||
driver_name = self._infra_driver_name(device_dict)
|
||||
instance_id = self._instance_id(device_dict)
|
||||
|
||||
e = None
|
||||
placement_attr = device_dict['placement_attr']
|
||||
region_name = placement_attr.get('region_name', None)
|
||||
try:
|
||||
self._device_manager.invoke(
|
||||
driver_name, 'delete_wait', plugin=self,
|
||||
context=context, device_id=instance_id)
|
||||
context=context, device_id=instance_id, auth_attr=auth_attr,
|
||||
region_name=region_name)
|
||||
except Exception as e_:
|
||||
e = e_
|
||||
device_dict['status'] = constants.ERROR
|
||||
@ -332,10 +357,12 @@ class VNFMPlugin(vm_db.VNFMPluginDb, VNFMMgmtMixin):
|
||||
|
||||
def delete_device(self, context, device_id):
|
||||
device_dict = self._delete_device_pre(context, device_id)
|
||||
vim_auth = self.get_vim(context, device_dict)
|
||||
self._vnf_monitor.delete_hosting_vnf(device_id)
|
||||
driver_name = self._infra_driver_name(device_dict)
|
||||
instance_id = self._instance_id(device_dict)
|
||||
|
||||
placement_attr = device_dict['placement_attr']
|
||||
region_name = placement_attr.get('region_name', None)
|
||||
kwargs = {
|
||||
mgmt_constants.KEY_ACTION: mgmt_constants.ACTION_DELETE_DEVICE,
|
||||
mgmt_constants.KEY_KWARGS: {'device': device_dict},
|
||||
@ -344,7 +371,10 @@ class VNFMPlugin(vm_db.VNFMPluginDb, VNFMMgmtMixin):
|
||||
self.mgmt_delete_pre(context, device_dict)
|
||||
self.mgmt_call(context, device_dict, kwargs)
|
||||
self._device_manager.invoke(driver_name, 'delete', plugin=self,
|
||||
context=context, device_id=instance_id)
|
||||
context=context,
|
||||
device_id=instance_id,
|
||||
auth_attr=vim_auth,
|
||||
region_name=region_name)
|
||||
except Exception as e:
|
||||
# TODO(yamahata): when the devaice is already deleted. mask
|
||||
# the error, and delete row in db
|
||||
@ -355,7 +385,7 @@ class VNFMPlugin(vm_db.VNFMPluginDb, VNFMMgmtMixin):
|
||||
self._delete_device_post(context, device_id, e)
|
||||
|
||||
self._delete_device_post(context, device_id, None)
|
||||
self.spawn_n(self._delete_device_wait, context, device_dict)
|
||||
self.spawn_n(self._delete_device_wait, context, device_dict, vim_auth)
|
||||
|
||||
def create_vnf(self, context, vnf):
|
||||
vnf['device'] = vnf.pop('vnf')
|
||||
|
107
tacker/vm/vim_client.py
Normal file
107
tacker/vm/vim_client.py
Normal file
@ -0,0 +1,107 @@
|
||||
# Copyright 2015-2016 Brocade Communications Systems Inc
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# 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
|
||||
|
||||
from cryptography.fernet import Fernet
|
||||
from oslo_config import cfg
|
||||
|
||||
from tacker.extensions import nfvo
|
||||
from tacker import manager
|
||||
from tacker.openstack.common import log as logging
|
||||
from tacker.plugins.common import constants
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
CONF = cfg.CONF
|
||||
|
||||
OPTS = [
|
||||
cfg.StrOpt(
|
||||
'default_vim', help=_('Default VIM for launching VNFs'))
|
||||
]
|
||||
cfg.CONF.register_opts(OPTS, 'nfvo_vim')
|
||||
|
||||
|
||||
class VimClient(object):
|
||||
def get_vim(self, context, vim_id=None, region_name=None):
|
||||
"""Get Vim information for provided VIM id
|
||||
|
||||
Initiate the NFVO plugin, request VIM information for the provided
|
||||
VIM id and validate region
|
||||
"""
|
||||
nfvo_plugin = manager.TackerManager.get_service_plugins().get(
|
||||
constants.NFVO)
|
||||
|
||||
if not vim_id:
|
||||
LOG.debug(_('VIM id not provided. Attempting to find default '
|
||||
'VIM id'))
|
||||
vim_name = cfg.CONF.nfvo_vim.default_vim
|
||||
if not vim_name:
|
||||
raise nfvo.VimDefaultIdException(
|
||||
message='Default VIM is not specified. Either specify a '
|
||||
'valid VIM in the VNF create or set default VIM in'
|
||||
' tacker.conf')
|
||||
try:
|
||||
vim_info = nfvo_plugin.get_vim_by_name(context, vim_name)
|
||||
except Exception:
|
||||
raise nfvo.VimDefaultIdException(
|
||||
vim_name=vim_name)
|
||||
else:
|
||||
try:
|
||||
vim_info = nfvo_plugin.get_vim(context, vim_id)
|
||||
except Exception:
|
||||
raise nfvo.VimNotFoundException(vim_id=vim_id)
|
||||
LOG.debug(_('VIM info found for vim id %s'), vim_id)
|
||||
if region_name and not self.region_valid(vim_info['placement_attr']
|
||||
['regions'], region_name):
|
||||
raise nfvo.VimRegionNotFoundException(region_name=region_name)
|
||||
|
||||
vim_auth = self._build_vim_auth(vim_info)
|
||||
vim_res = {'vim_auth': vim_auth, 'vim_id': vim_info['id'],
|
||||
'vim_name': vim_info.get('name', vim_info['id'])}
|
||||
return vim_res
|
||||
|
||||
@staticmethod
|
||||
def region_valid(vim_regions, region_name):
|
||||
return region_name in vim_regions
|
||||
|
||||
def _build_vim_auth(self, vim_info):
|
||||
LOG.debug('VIM id is %s', vim_info['id'])
|
||||
vim_auth = vim_info['auth_cred']
|
||||
vim_auth['password'] = self._decode_vim_auth(vim_info['id'],
|
||||
vim_auth[
|
||||
'password'].encode(
|
||||
'utf-8'))
|
||||
vim_auth['auth_url'] = vim_info['auth_url']
|
||||
return vim_auth
|
||||
|
||||
def _decode_vim_auth(self, vim_id, cred):
|
||||
"""Decode Vim credentials
|
||||
|
||||
Decrypt VIM cred. using Fernet Key
|
||||
"""
|
||||
vim_key = self._find_vim_key(vim_id)
|
||||
f = Fernet(vim_key)
|
||||
if not f:
|
||||
LOG.warn(_('Unable to decode VIM auth'))
|
||||
raise nfvo.VimNotFoundException('Unable to decode VIM auth key')
|
||||
return f.decrypt(cred)
|
||||
|
||||
@staticmethod
|
||||
def _find_vim_key(vim_id):
|
||||
key_file = os.path.join(CONF.vim_keys.openstack, vim_id)
|
||||
LOG.debug(_('Attempting to open key file for vim id %s'), vim_id)
|
||||
with open(key_file, 'r') as f:
|
||||
return f.read()
|
||||
LOG.warn(_('VIM id invalid or key not found for %s'), vim_id)
|
Loading…
Reference in New Issue
Block a user