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 |