Implements auto flavor creation

Partially-implements: automatic-resource-creation
Change-Id: I59526f4868fb0189f963dc146d13d24831518b4d
This commit is contained in:
Bharath Thiruveedula 2016-03-21 14:11:47 +05:30
parent d6b102b201
commit 5ffbb36ee4
17 changed files with 544 additions and 23 deletions

View File

@ -0,0 +1,37 @@
tosca_definitions_version: tosca_simple_profile_for_nfv_1_0_0
description: Demo example
metadata:
template_name: sample-tosca-vnfd
topology_template:
node_templates:
VDU1:
type: tosca.nodes.nfv.VDU.Tacker
capabilities:
nfv_compute:
properties:
disk_size: 1 GB
mem_size: 512 MB
num_cpus: 2
properties:
image: cirros-0.3.4-x86_64-uec
mgmt_driver: noop
availability_zone: nova
CP1:
type: tosca.nodes.nfv.CP.Tacker
properties:
management: true
requirements:
- virtualLink:
node: VL1
- virtualBinding:
node: VDU1
VL1:
type: tosca.nodes.nfv.VL
properties:
network_name: net1
vendor: Tacker

View File

@ -41,6 +41,28 @@ from tacker.openstack.common import log as logging
TIME_FORMAT = "%Y-%m-%dT%H:%M:%SZ"
LOG = logging.getLogger(__name__)
SYNCHRONIZED_PREFIX = 'tacker-'
MEM_UNITS = {
"MB": {
"MB": {
"op": "*",
"val": "1"
},
"GB": {
"op": "/",
"val": "1024"
}
},
"GB": {
"MB": {
"op": "*",
"val": "1024"
},
"GB": {
"op": "*",
"val": "1"
}
}
}
synchronized = lockutils.synchronized_with_prefix(SYNCHRONIZED_PREFIX)
@ -308,3 +330,20 @@ def is_valid_ipv4(address):
return netaddr.valid_ipv4(address)
except Exception:
return False
def change_memory_unit(mem, to):
"""Changes the memory value(mem) based on the unit('to') specified.
If the unit is not specified in 'mem', by default, it is considered
as "MB". And this method returns only integer.
"""
mem = str(mem) + " MB" if str(mem).isdigit() else mem.upper()
for unit, value in MEM_UNITS.iteritems():
mem_arr = mem.split(unit)
if len(mem_arr) < 2:
continue
return eval(mem_arr[0] +
MEM_UNITS[unit][to]["op"] +
MEM_UNITS[unit][to]["val"])

View File

@ -381,3 +381,15 @@ class TestDict2Tuples(base.BaseTestCase):
expected = ((42, 'baz'), ('aaa', 'zzz'), ('foo', 'bar'))
output_tuple = utils.dict2tuple(input_dict)
self.assertEqual(expected, output_tuple)
class TestChangeMemory(testtools.TestCase):
def test_change_memory_from_mb_to_gb(self):
actual_val = utils.change_memory_unit("1024 mb", "GB")
expected_val = 1
self.assertEqual(actual_val, expected_val)
def test_change_memory_from_gb_to_mb(self):
actual_val = utils.change_memory_unit("1 GB", "MB")
expected_val = 1024
self.assertEqual(actual_val, expected_val)

View File

@ -0,0 +1,32 @@
heat_template_version: 2013-05-23
description: >
OpenWRT with services
outputs:
mgmt_ip-VDU1:
value:
get_attr: [CP1, fixed_ips, 0, ip_address]
parameters: {}
resources:
VDU1:
type: OS::Nova::Server
properties:
config_drive: false
flavor: {get_resource: VDU1_flavor}
image: OpenWRT
networks:
- port:
get_resource: CP1
user_data_format: SOFTWARE_CONFIG
CP1:
type: OS::Neutron::Port
properties:
network: existing_network_1
VDU1_flavor:
type: OS::Nova::Flavor
properties:
disk: 10
ram: 512
vcpus: 2

View File

@ -0,0 +1,26 @@
heat_template_version: 2013-05-23
description: >
OpenWRT with services
outputs:
mgmt_ip-VDU1:
value:
get_attr: [CP1, fixed_ips, 0, ip_address]
parameters: {}
resources:
VDU1:
type: OS::Nova::Server
properties:
config_drive: false
flavor: m1.nano
image: OpenWRT
networks:
- port:
get_resource: CP1
user_data_format: SOFTWARE_CONFIG
CP1:
type: OS::Neutron::Port
properties:
network: existing_network_1

View File

@ -0,0 +1,32 @@
heat_template_version: 2013-05-23
description: >
OpenWRT with services
outputs:
mgmt_ip-VDU1:
value:
get_attr: [CP1, fixed_ips, 0, ip_address]
parameters: {}
resources:
VDU1:
type: OS::Nova::Server
properties:
config_drive: false
flavor: {get_resource: VDU1_flavor}
image: OpenWRT
networks:
- port:
get_resource: CP1
user_data_format: SOFTWARE_CONFIG
CP1:
type: OS::Neutron::Port
properties:
network: existing_network_1
VDU1_flavor:
type: OS::Nova::Flavor
properties:
disk: 10
ram: 512
vcpus: 1

View File

@ -0,0 +1,33 @@
heat_template_version: 2013-05-23
description: >
OpenWRT with services
outputs:
mgmt_ip-VDU1:
value:
get_attr: [CP1, fixed_ips, 0, ip_address]
parameters: {}
resources:
VDU1:
type: OS::Nova::Server
properties:
config_drive: false
flavor: {get_resource: VDU1_flavor}
image: OpenWRT
networks:
- port:
get_resource: CP1
user_data_format: SOFTWARE_CONFIG
CP1:
type: OS::Neutron::Port
properties:
network: existing_network_1
VDU1_flavor:
type: OS::Nova::Flavor
properties:
disk: 2
ram: 512
vcpus: 2

View File

@ -0,0 +1,44 @@
tosca_definitions_version: tosca_simple_profile_for_nfv_1_0_0
description: OpenWRT with services
metadata:
template_name: OpenWRT
topology_template:
node_templates:
VDU1:
type: tosca.nodes.nfv.VDU.Tacker
capabilities:
nfv_compute:
properties:
num_cpus: 2
disk_size: 10 GB
mem_size: 512 MB
properties:
image: OpenWRT
mgmt_driver: openwrt
monitoring_policy:
name: ping
actions:
- {trigger: failure, action: respawn}
parameters:
count: "3"
interval: "10"
CP1:
type: tosca.nodes.nfv.CP.Tacker
properties:
management: true
requirements:
- virtualLink:
node: VL1
- virtualBinding:
node: VDU1
VL1:
type: tosca.nodes.nfv.VL
properties:
network_name: existing_network_1
vendor: ACME

View File

@ -0,0 +1,44 @@
tosca_definitions_version: tosca_simple_profile_for_nfv_1_0_0
description: OpenWRT with services
metadata:
template_name: OpenWRT
topology_template:
node_templates:
VDU1:
type: tosca.nodes.nfv.VDU.Tacker
capabilities:
nfv_compute:
properties:
num_cpus: 2
disk_size: 10 GB
mem_size: 512 MB
properties:
image: OpenWRT
mgmt_driver: openwrt
flavor: m1.nano
monitoring_policy:
name: ping
actions:
- {trigger: failure, action: respawn}
parameters:
count: "3"
interval: "10"
CP1:
type: tosca.nodes.nfv.CP.Tacker
properties:
management: true
requirements:
- virtualLink:
node: VL1
- virtualBinding:
node: VDU1
VL1:
type: tosca.nodes.nfv.VL
properties:
network_name: existing_network_1
vendor: ACME

View File

@ -0,0 +1,41 @@
tosca_definitions_version: tosca_simple_profile_for_nfv_1_0_0
description: OpenWRT with services
metadata:
template_name: OpenWRT
topology_template:
node_templates:
VDU1:
type: tosca.nodes.nfv.VDU.Tacker
capabilities:
nfv_compute:
properties:
disk_size: 10 GB
properties:
image: OpenWRT
mgmt_driver: openwrt
monitoring_policy:
name: ping
actions:
- {trigger: failure, action: respawn}
parameters:
count: "3"
interval: "10"
CP1:
type: tosca.nodes.nfv.CP.Tacker
properties:
management: true
requirements:
- virtualLink:
node: VL1
- virtualBinding:
node: VDU1
VL1:
type: tosca.nodes.nfv.VL
properties:
network_name: existing_network_1
vendor: ACME

View File

@ -0,0 +1,43 @@
tosca_definitions_version: tosca_simple_profile_for_nfv_1_0_0
description: OpenWRT with services
metadata:
template_name: OpenWRT
topology_template:
node_templates:
VDU1:
type: tosca.nodes.nfv.VDU.Tacker
capabilities:
nfv_compute:
properties:
num_cpus: 2
disk_size: 2048
mem_size: 512
properties:
image: OpenWRT
mgmt_driver: openwrt
monitoring_policy:
name: ping
actions:
- {trigger: failure, action: respawn}
parameters:
count: "3"
interval: "10"
CP1:
type: tosca.nodes.nfv.CP.Tacker
properties:
management: true
requirements:
- virtualLink:
node: VL1
- virtualBinding:
node: VDU1
VL1:
type: tosca.nodes.nfv.VL
properties:
network_name: existing_network_1
vendor: ACME

View File

@ -17,6 +17,7 @@ import codecs
import mock
import os
import testtools
import yaml
from tacker import context
from tacker.tests.unit.db import utils
@ -49,8 +50,6 @@ class TestDeviceHeat(testtools.TestCase):
hot_param_template = _get_template('hot_openwrt_params.yaml')
hot_ipparam_template = _get_template('hot_openwrt_ipparams.yaml')
vnfd_openwrt = _get_template('openwrt.yaml')
tosca_openwrt = _get_template('test_tosca_openwrt.yaml')
hot_tosca_openwrt = _get_template('hot_tosca_openwrt.yaml')
config_data = _get_template('config_data.yaml')
def setUp(self):
@ -192,22 +191,25 @@ class TestDeviceHeat(testtools.TestCase):
self.assertEqual(device_obj, expected_device_update)
def test_create_device_template_pre_tosca(self):
dtemplate = self._get_device_template(self.tosca_openwrt)
exp_tmpl = self._get_expected_device_template(self.tosca_openwrt)
tosca_tpl = _get_template('test_tosca_openwrt.yaml')
dtemplate = self._get_device_template(tosca_tpl)
exp_tmpl = self._get_expected_device_template(tosca_tpl)
self.heat_driver.create_device_template_pre(None, None, dtemplate)
self.assertEqual(dtemplate, exp_tmpl)
def _get_expected_fields_tosca(self):
def _get_expected_fields_tosca(self, template):
return {'stack_name':
'tacker.vm.infra_drivers.heat.heat_DeviceHeat-eb84260e'
'-5ff7-4332-b032-50a14d6c1123',
'template': self.hot_tosca_openwrt}
'template': _get_template(template)}
def _get_expected_tosca_device(self):
exp_tmpl = self._get_expected_device_template(self.tosca_openwrt)
def _get_expected_tosca_device(self, tosca_tpl_name, hot_tpl_name):
tosca_tpl = _get_template(tosca_tpl_name)
exp_tmpl = self._get_expected_device_template(tosca_tpl)
tosca_hw_dict = yaml.safe_load(_get_template(hot_tpl_name))
return {'device_template': exp_tmpl['device_template'],
'description': u'OpenWRT with services',
'attributes': {'heat_template': self.hot_tosca_openwrt,
'attributes': {'heat_template': tosca_hw_dict,
'monitoring_policy': '{"vdus": {"VDU1":'
' {"name": "ping", "actions": [{"trigger":'
' "failure", "action": "respawn"}],'
@ -220,10 +222,8 @@ class TestDeviceHeat(testtools.TestCase):
'template_id': u'eb094833-995e-49f0-a047-dfb56aaf7c4e',
'tenant_id': u'ad7ebc56538745a08ef7c5e97f8bd437'}
def _get_dummy_tosca_device(self, template=''):
tosca_template = getattr(self, 'tosca_' + template, None)
if not tosca_template:
tosca_template = self.tosca_openwrt
def _get_dummy_tosca_device(self, template):
tosca_template = _get_template(template)
device = utils.get_dummy_device_obj()
dtemplate = self._get_expected_device_template(tosca_template)
dtemplate['service_types'] = [{'service_type': 'vnfd', 'id':
@ -232,14 +232,46 @@ class TestDeviceHeat(testtools.TestCase):
device['device_template'] = dtemplate['device_template']
return device
def test_create_tosca(self):
# self.skipTest("Not ready yet")
device = self._get_dummy_tosca_device()
def _test_assert_equal_for_tosca_templates(self, tosca_tpl_name,
hot_tpl_name):
device = self._get_dummy_tosca_device(tosca_tpl_name)
expected_result = '4a4c2d44-8a52-4895-9a75-9d1c76c3e738'
expected_fields = self._get_expected_fields_tosca()
expected_device = self._get_expected_tosca_device()
expected_fields = self._get_expected_fields_tosca(hot_tpl_name)
expected_device = self._get_expected_tosca_device(tosca_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)
# self.heat_client.create.assert_called_once_with(expected_fields)
actual_fields = self.heat_client.create.call_args[0][0]
actual_fields["template"] = yaml.safe_load(actual_fields["template"])
expected_fields["template"] = \
yaml.safe_load(expected_fields["template"])
self.assertEqual(actual_fields, expected_fields)
device["attributes"]["heat_template"] = yaml.safe_load(
device["attributes"]["heat_template"])
self.assertEqual(expected_result, result)
self.assertEqual(device, expected_device)
def test_create_tosca(self):
# self.skipTest("Not ready yet")
self._test_assert_equal_for_tosca_templates('test_tosca_openwrt.yaml',
'hot_tosca_openwrt.yaml')
def test_create_tosca_with_new_flavor(self):
self._test_assert_equal_for_tosca_templates('test_tosca_flavor.yaml',
'hot_flavor.yaml')
def test_create_tosca_with_new_flavor_with_defaults(self):
self._test_assert_equal_for_tosca_templates(
'test_tosca_flavor_defaults.yaml',
'hot_flavor_defaults.yaml')
def test_create_tosca_with_flavor_and_capabilities(self):
self._test_assert_equal_for_tosca_templates(
'test_tosca_flavor_and_capabilities.yaml',
'hot_flavor_and_capabilities.yaml')
def test_create_tosca_with_flavor_no_units(self):
self._test_assert_equal_for_tosca_templates(
'test_tosca_flavor_no_units.yaml',
'hot_flavor_no_units.yaml')

View File

@ -35,6 +35,7 @@ class TestToscaUtils(testtools.TestCase):
toscautils.updateimports(vnfd_dict)
tosca = ToscaTemplate(parsed_params={}, a_file=False,
yaml_dict_tpl=vnfd_dict)
tosca_flavor = _get_template('test_tosca_flavor.yaml')
def setUp(self):
super(TestToscaUtils, self).setUp()
@ -106,7 +107,7 @@ class TestToscaUtils(testtools.TestCase):
expected_heat_tpl = _get_template('hot_tosca_openwrt.yaml')
mgmt_ports = toscautils.get_mgmt_ports(self.tosca)
heat_tpl = toscautils.post_process_heat_template(
heat_template_yaml, mgmt_ports)
heat_template_yaml, mgmt_ports, {})
heatdict = yaml.load(heat_tpl)
expecteddict = yaml.load(expected_heat_tpl)
@ -120,3 +121,33 @@ class TestToscaUtils(testtools.TestCase):
for vdu in vdus:
self.assertEqual(vdu.type_definition.is_derived_from(
toscautils.TACKERVDU), True)
def test_get_flavor_dict(self):
vnfd_dict = yaml.load(self.tosca_flavor)
toscautils.updateimports(vnfd_dict)
tosca = ToscaTemplate(a_file=False, yaml_dict_tpl=vnfd_dict)
expected_flavor_dict = {
"VDU1": {
"vcpus": 2,
"disk": 10,
"ram": 512
}
}
actual_flavor_dict = toscautils.get_flavor_dict(tosca)
self.assertEqual(expected_flavor_dict, actual_flavor_dict)
def test_add_resources_tpl_for_flavor(self):
dummy_heat_dict = yaml.load(_get_template(
'hot_flavor_and_capabilities.yaml'))
expected_dict = yaml.load(_get_template('hot_flavor.yaml'))
dummy_heat_res = {
"flavor": {
"VDU1": {
"vcpus": 2,
"ram": 512,
"disk": 10
}
}
}
toscautils.add_resources_tpl(dummy_heat_dict, dummy_heat_res)
self.assertEqual(dummy_heat_dict, expected_dict)

View File

@ -273,6 +273,7 @@ class DeviceHeat(abstract_driver.DeviceAbstractDriver):
monitoring_dict = toscautils.get_vdu_monitoring(tosca)
mgmt_ports = toscautils.get_mgmt_ports(tosca)
res_tpl = toscautils.get_resources_dict(tosca)
toscautils.post_process_template(tosca)
try:
translator = TOSCATranslator(tosca, parsed_params)
@ -281,7 +282,7 @@ class DeviceHeat(abstract_driver.DeviceAbstractDriver):
LOG.debug("heat-translator error: %s", str(e))
raise vnfm.HeatTranslatorFailed(error_msg_details=str(e))
heat_template_yaml = toscautils.post_process_heat_template(
heat_template_yaml, mgmt_ports)
heat_template_yaml, mgmt_ports, res_tpl)
else:
assert 'template' not in fields
assert 'template_url' not in fields

View File

@ -26,6 +26,15 @@ data_types:
entry_schema:
type: string
tosca.datatypes.compute_properties:
properties:
num_cpus:
type: integer
mem_size:
type: string
disk_size:
type: string
policy_types:
tosca.policies.tacker.Placement:
derived_from: tosca.policies.Root

View File

@ -1,6 +1,9 @@
node_types:
tosca.nodes.nfv.VDU.Tacker:
derived_from: tosca.nodes.nfv.VDU
capabilities:
nfv_compute:
type: tosca.datatypes.compute_properties
properties:
image:
# type: tosca.artifacts.Deployment.Image.VM

View File

@ -12,9 +12,11 @@
# under the License.
import os
import sys
import yaml
from tacker.common import log
from tacker.common import utils
from tacker.extensions import vnfm
from tacker.openstack.common import log as logging
@ -30,6 +32,15 @@ TACKERCP = 'tosca.nodes.nfv.CP.Tacker'
TACKERVDU = 'tosca.nodes.nfv.VDU.Tacker'
TOSCA_BINDS_TO = 'tosca.relationships.network.BindsTo'
VDU = 'tosca.nodes.nfv.VDU'
OS_RESOURCES = {
'flavor': 'get_flavor_dict'
}
FLAVOR_PROPS = {
"num_cpus": ("vcpus", 1, None),
"disk_size": ("disk", 1, "GB"),
"mem_size": ("ram", 512, "MB")
}
delpropmap = {TACKERVDU: ('mgmt_driver', 'config', 'service_type',
@ -42,6 +53,10 @@ convert_prop = {TACKERCP: {'anti_spoofing_protection':
deletenodes = (MONITORING, FAILURE, PLACEMENT)
HEAT_RESOURCE_MAP = {
"flavor": "OS::Nova::Flavor"
}
@log.log
def updateimports(template):
@ -95,7 +110,24 @@ def get_mgmt_ports(tosca):
@log.log
def post_process_heat_template(heat_tpl, mgmt_ports):
def add_resources_tpl(heat_dict, hot_res_tpl):
for res, res_dict in hot_res_tpl.iteritems():
for vdu, vdu_dict in res_dict.iteritems():
res_name = vdu + "_" + res
heat_dict["resources"][res_name] = {
"type": HEAT_RESOURCE_MAP[res],
"properties": {}
}
for prop, val in vdu_dict.iteritems():
heat_dict["resources"][res_name]["properties"][prop] = val
heat_dict["resources"][vdu]["properties"][res] = {
"get_resource": res_name
}
@log.log
def post_process_heat_template(heat_tpl, mgmt_ports, res_tpl):
heat_dict = yamlparser.simple_ordered_parse(heat_tpl)
for outputname, portname in mgmt_ports.items():
ipval = {'get_attr': [portname, 'fixed_ips', 0, 'ip_address']}
@ -105,7 +137,7 @@ def post_process_heat_template(heat_tpl, mgmt_ports):
else:
heat_dict['outputs'] = output
LOG.debug(_('Added output for %s') % outputname)
add_resources_tpl(heat_dict, res_tpl)
return yaml.dump(heat_dict)
@ -156,3 +188,33 @@ def findvdus(template):
if nt.type_definition.is_derived_from(TACKERVDU):
vdus.append(nt)
return vdus
def get_flavor_dict(template):
flavor_dict = {}
vdus = findvdus(template)
for nt in vdus:
flavor_tmp = nt.get_properties().get('flavor', None)
if flavor_tmp:
continue
if nt.get_capabilities().get("nfv_compute", None):
flavor_dict[nt.name] = {}
properties = nt.get_capabilities()["nfv_compute"].get_properties()
for prop, (hot_prop, default, unit) in \
FLAVOR_PROPS.iteritems():
hot_prop_val = (properties[prop].value
if properties.get(prop, None) else None)
if unit and hot_prop_val:
hot_prop_val = \
utils.change_memory_unit(hot_prop_val, unit)
flavor_dict[nt.name][hot_prop] = \
hot_prop_val if hot_prop_val else default
return flavor_dict
def get_resources_dict(template):
res_dict = dict()
for res, method in OS_RESOURCES.iteritems():
res_dict[res] = getattr(sys.modules[__name__],
method)(template)
return res_dict