ranger/orm/services/flavor_manager/fms_rest/data/wsme/models.py

645 lines
22 KiB
Python
Executable File

import wsme
from orm.common.orm_common.utils.cross_api_utils import (set_utils_conf,
get_regions_of_group,
validate_description)
from orm.services.flavor_manager.fms_rest.data.sql_alchemy import db_models
from orm.services.flavor_manager.fms_rest.data.wsme.model import Model
from orm.services.flavor_manager.fms_rest.logic.error_base import ErrorStatus
from oslo_config import cfg
from pecan import conf, request
class TenantWrapper(Model):
"""user model the customer
"""
tenants = wsme.wsattr([str], mandatory=False)
def __init__(self, tenants=[]): # pragma: no cover
"""region array
:param tenants: array of tenants
"""
self.tenants = tenants
class ExtraSpecsWrapper(Model):
"""extra spec model."""
os_extra_specs = wsme.wsattr(wsme.types.DictType(str, str),
mandatory=False)
extra_specs = wsme.wsattr(wsme.types.DictType(str, str),
mandatory=False, name="extra-specs")
def __init__(self, os_extra_specs={}, extra_specs={}):
"""init func."""
# self.extra_specs = extra_specs
if extra_specs:
self.extra_specs = extra_specs
if os_extra_specs:
self.os_extra_specs = os_extra_specs
def to_db_model(self):
extra_spec = []
autogen_es = cfg.CONF.flavor_series_metadata.autogen_extra_specs
for key, value in self.os_extra_specs.iteritems():
if key in autogen_es:
continue
extra_spec_rec = db_models.FlavorExtraSpec()
extra_spec_rec.key_name = key
extra_spec_rec.key_value = value
extra_spec.append(extra_spec_rec)
return extra_spec
def _get_extra_specs_urlpath(self):
# return the way to return extra specs depends on url used
return '{}'.format(request.upath_info).split("/")[-1].strip()
@staticmethod
def from_db_model(os_extra_specs):
extra_spec_method = ExtraSpecsWrapper()._get_extra_specs_urlpath()
extra_specs = ExtraSpecsWrapper()
setattr(extra_specs, extra_spec_method, {})
for extra_spec in os_extra_specs:
getattr(extra_specs,
extra_spec_method,
None)[extra_spec.key_name] = extra_spec.key_value
return extra_specs
class TagsWrapper(Model):
"""Tags model."""
tags = wsme.wsattr(wsme.types.DictType(str, str), mandatory=True)
def __init__(self, tags={}):
"""init func."""
self.tags = tags
def to_db_model(self):
tag = []
for key, value in self.tags.iteritems():
tags_rec = db_models.FlavorTag()
tags_rec.key_name = key
tags_rec.key_value = value
tag.append(tags_rec)
return tag
@staticmethod
def from_db_model(tags):
my_tags = TagsWrapper()
for tag in tags:
my_tags.tags[tag.key_name] = tag.key_value
return my_tags
class Region(Model):
"""network model the customer
"""
name = wsme.wsattr(str, mandatory=True)
type = wsme.wsattr(str, default="single", mandatory=False)
status = wsme.wsattr(str, mandatory=False)
error_message = wsme.wsattr(str, mandatory=False)
def __init__(self, name="", type="single", status="", error_message=""):
"""region array
:param name: region names
:param type: region type
:param status: region creation status
:param error_message: region creation error status message
"""
self.name = name
self.type = type
self.status = status
if error_message:
self.error_message = error_message
def to_db_model(self):
if self.name == '' or self.name.isspace():
raise ErrorStatus(400, 'Cannot add region with empty name')
region_rec = db_models.FlavorRegion()
region_rec.region_name = self.name
region_rec.region_type = self.type
return region_rec
class RegionWrapper(Model): # pragma: no cover
"""regions model
"""
regions = wsme.wsattr([Region], mandatory=False)
def __init__(self, regions=[]):
"""init
:param regions: array of regions
"""
self.regions = regions
class Flavor(Model):
"""flavor entity with all it's related data
"""
id = wsme.wsattr(wsme.types.text, mandatory=False)
name = wsme.wsattr(wsme.types.text, mandatory=False)
alias = wsme.wsattr(wsme.types.text, mandatory=False)
description = wsme.wsattr(wsme.types.text, mandatory=False)
series = wsme.wsattr(wsme.types.text, mandatory=True)
ram = wsme.wsattr(wsme.types.text, mandatory=True)
vcpus = wsme.wsattr(wsme.types.text, mandatory=True)
disk = wsme.wsattr(wsme.types.text, mandatory=True)
swap = wsme.wsattr(wsme.types.text, mandatory=False)
ephemeral = wsme.wsattr(wsme.types.text, mandatory=False)
regions = wsme.wsattr(wsme.types.ArrayType(Region), mandatory=False)
visibility = wsme.wsattr(wsme.types.text, mandatory=True)
tenants = wsme.wsattr(wsme.types.ArrayType(str), mandatory=False)
status = wsme.wsattr(wsme.types.text, mandatory=False)
tag = wsme.wsattr(wsme.types.DictType(str, str), mandatory=False)
options = wsme.wsattr(wsme.types.DictType(str, str), mandatory=False)
extra_specs = wsme.wsattr(wsme.types.DictType(str, str), mandatory=False,
name="extra-specs")
def __init__(self,
id="",
name="",
alias=None,
description="",
series="",
ram="0",
vcpus="0",
disk="0",
swap="0",
ephemeral="0",
extra_specs={},
tag={},
options={},
regions=[],
visibility="",
tenants=[],
status=""):
"""Create a new Flavor.
:param id: flavor UUID
:param name: name extracted from flavor parameters
:param description: flavor description
:param series: series possible
:param ram: RAM in MB
:param vcpus:
:param disk: Disk in GB
:param swap: is optional and default is 0
:param ephemeral: is optional and default is 0, size in GB
:param extra_spec: key-value dictionary
:param tags: key-value dictionary
:param options: key-value dictionary
:param regions:
:param visibility:
:param tenants:
:param status: status of creation
"""
self.id = id
self.name = name
if alias is not None:
self.alias = alias
self.description = description
self.series = series
self.ram = ram
self.vcpus = vcpus
self.disk = disk
self.swap = swap
self.ephemeral = ephemeral
self.extra_specs = extra_specs
self.tag = tag
self.options = options
self.regions = regions
self.visibility = visibility
self.tenants = tenants
self.status = status
def validate_model(self, context=None):
series_metadata = {}
valid_numa = []
valid_vnf = []
if self.series:
valid_flavor_series = cfg.CONF.fms.flavor_series
if self.series not in valid_flavor_series:
raise ErrorStatus(400, "Series possible values are {}".format(
valid_flavor_series))
else:
raise ErrorStatus(400, "Series not specified.")
if self.series in cfg.CONF['flavor_series_metadata']:
series_metadata = cfg.CONF['flavor_series_metadata'][self.series]
else:
raise ErrorStatus(400, "Cannot retrieve requested flavor"
" series metadata.")
if 'valid_options_numa' in series_metadata:
valid_numa = [x for x in
series_metadata['valid_options_numa'].split(',')]
if 'valid_options_vnf' in series_metadata:
valid_vnf = [x for x in
series_metadata['valid_options_vnf'].split(',')]
# validate that input entries are valid
try:
# flavor option values must be either 'true' or 'false' (in quotes)
if self.options:
option_values = self.options.values()
invalid_opt_vals = [x for x in option_values if (x.lower()
not in ['true', 'false'])]
if invalid_opt_vals:
raise ErrorStatus(400, "All flavor option values must have"
" a value of 'true' or 'false'")
# validate series and set flavor vcpu and vram limits
requested_numa = [n for n in valid_numa if n in
self.options.keys() and
self.options[n].lower() == 'true']
if requested_numa:
vcpu_limit = int(series_metadata['vcpu_limit'])
vram_limit = int(series_metadata['vram_limit'])
else:
vcpu_limit = int(series_metadata['alt_vcpu_limit'])
vram_limit = int(series_metadata['alt_vram_limit'])
# determine other flavor limits
swap_file_limit = cfg.CONF.fms.flavor_swap_file_limit
ephemeral_limit = cfg.CONF.fms.flavor_ephemeral_limit
isValid = validate_description(self.description)
if not isValid:
raise ErrorStatus(400, "Flavor description does not allow"
" special characters: only dashes,"
" commas, and period allowed.")
if not self.ram.isdigit():
raise ErrorStatus(400, "ram must be a number")
if not self.vcpus.isdigit():
raise ErrorStatus(400, "vcpus must be a number")
if not self.validInt(self.disk):
raise ErrorStatus(400, "disk must be a number")
if not self.swap.isdigit():
raise ErrorStatus(400, "swap must be a number")
if self.ephemeral and not self.ephemeral.isdigit():
raise ErrorStatus(400, "ephemeral must be a number")
if int(self.ram) not in range(1024, vram_limit + 1, 1024):
raise ErrorStatus(400,
"ram value is out of range. Expected range"
" is 1024(1GB)-%6d(%3dGB) and must be a"
" multiple of 1024" %
(vram_limit, vram_limit / 1024))
if int(self.vcpus) not in range(1, vcpu_limit + 1):
raise ErrorStatus(400, "vcpus value is out of range. Expected"
"range is 1-%2d" % (vcpu_limit))
if int(self.disk) < 0:
raise ErrorStatus(400, "disk cannot be less than zero")
if not self.ephemeral:
self.ephemeral = "0"
elif (self.ephemeral and
int(self.ephemeral) not in range(0, ephemeral_limit + 1)):
raise ErrorStatus(400,
"ephemeral value is out of range. Expected"
" range is 0-%5d(%2dTB)" %
(ephemeral_limit, ephemeral_limit / 1000))
if int(self.swap) not in range(0, swap_file_limit + 1, 1024):
raise ErrorStatus(400,
"swap value is out of range. Expected"
" range is 0-%6d(%3dGB) and must be a"
" multiple of 1024" %
(swap_file_limit, swap_file_limit / 1024))
except ValueError:
raise ErrorStatus(400, "ram, vcpus, disk, ephemeral and swap must"
" be integers")
for symbol, value in self.extra_specs.iteritems():
if symbol == 'numa_override' and value not in valid_numa:
raise ErrorStatus(400,
"Invalid value. numa_override possible"
" values: " + str(valid_numa))
if symbol == 'vlan_category' and value not in valid_vnf:
raise ErrorStatus(400,
"Invalid value. vlan_category possible"
" values: " + str(valid_vnf))
# region type can be group only in create flavor!!
if not context == "create":
for region in self.regions:
if region.type == "group":
raise ErrorStatus(400,
"region type \'group\' is invalid in"
" this action, \'group\' can be only"
" in create flavor action")
def to_db_model(self):
flavor = db_models.Flavor()
extra_spec = []
tags = []
options = []
regions = []
tenants = []
autogen_es = cfg.CONF.flavor_series_metadata.autogen_extra_specs
for symbol, value in self.extra_specs.iteritems():
if symbol in autogen_es:
continue
es = db_models.FlavorExtraSpec()
es.key_name = symbol
es.key_value = value
extra_spec.append(es)
if self.series:
extra_spec.extend(self.get_extra_spec_needed())
for symbol, value in self.tag.iteritems():
tag = db_models.FlavorTag()
tag.key_name = symbol
tag.key_value = value
tags.append(tag)
for symbol, value in self.options.iteritems():
option = db_models.FlavorOption()
option.key_name = symbol
option.key_value = value.lower()
options.append(option)
for region in self.regions:
regions.append(region.to_db_model())
for tenant in self.tenants:
tenant_rec = db_models.FlavorTenant()
tenant_rec.tenant_id = tenant
tenants.append(tenant_rec)
flavor.id = self.id
flavor.name = self.name
if self.alias is not wsme.Unset:
flavor.alias = self.alias
flavor.description = self.description
flavor.series = self.series
flavor.ram = int(self.ram)
flavor.vcpus = int(self.vcpus)
flavor.disk = int(self.disk)
flavor.swap = int(self.swap)
flavor.ephemeral = int(self.ephemeral)
flavor.visibility = self.visibility
flavor.flavor_extra_specs = extra_spec
flavor.flavor_tags = tags
flavor.flavor_options = options
flavor.flavor_regions = regions
flavor.flavor_tenants = tenants
return flavor
@staticmethod
def from_db_model(sql_flavor):
flavor = Flavor()
flavor.id = sql_flavor.id
flavor.series = sql_flavor.series
flavor.description = sql_flavor.description
flavor.ram = str(sql_flavor.ram)
flavor.ephemeral = str(sql_flavor.ephemeral)
flavor.visibility = sql_flavor.visibility
flavor.vcpus = str(sql_flavor.vcpus)
flavor.swap = str(sql_flavor.swap)
flavor.disk = str(sql_flavor.disk)
flavor.name = sql_flavor.name
if sql_flavor.alias is not None:
flavor.alias = sql_flavor.alias
for tenant in sql_flavor.flavor_tenants:
flavor.tenants.append(tenant.tenant_id)
for sql_region in sql_flavor.flavor_regions:
region = Region()
region.name = sql_region.region_name
region.type = sql_region.region_type
flavor.regions.append(region)
for extra_spec in sql_flavor.flavor_extra_specs:
flavor.extra_specs[extra_spec.key_name] = extra_spec.key_value
for tag in sql_flavor.flavor_tags:
flavor.tag[tag.key_name] = tag.key_value
for option in sql_flavor.flavor_options:
flavor.options[option.key_name] = option.key_value
return flavor
def get_extra_spec_needed(self):
extra_spec_needed = []
requested_options = []
mixed_options = {}
series_metadata = cfg.CONF['flavor_series_metadata'][self.series]
# Retreive default extra specs and mixed options for series
for f_key, f_val in series_metadata.items():
if f_key.startswith("es_default_"):
es = db_models.FlavorExtraSpec(key_name_value=f_val)
extra_spec_needed.append(es)
if f_key.startswith("es_mixed_"):
mixed_es_trimmed = f_key.replace('es_mixed_', '')
mixed_options[mixed_es_trimmed] = f_val
# Evaluate numa options
if 'valid_options_numa' in series_metadata:
valid_numa = [x for x in
series_metadata['valid_options_numa'].split(',')]
option_numa = [n for n in valid_numa if n in
self.options.keys() and
self.options[n].lower() == 'true']
if not option_numa:
es = db_models.FlavorExtraSpec(
key_name_value=series_metadata['es_alt_numa_nodes'])
extra_spec_needed.append(es)
else:
es = db_models.FlavorExtraSpec(
key_name_value=series_metadata['es_numa_nodes'])
extra_spec_needed.append(es)
requested_options.extend(option_numa)
# Evaluate pci options
if 'valid_options_pci' in series_metadata:
valid_pci = [x for x in
series_metadata['valid_options_pci'].split(',')]
option_pci = [n for n in valid_pci if n in
self.options.keys() and
self.options[n].lower() == 'true']
if option_pci:
requested_options.extend(option_pci)
# Evaluate thread options
if 'valid_options_thread' in series_metadata:
valid_thread = [x for x in
series_metadata['valid_options_thread'].split(',')]
option_thread = [n for n in valid_thread if n in
self.options.keys() and
self.options[n].lower() == 'true' and
self.visibility.lower() == 'private']
if option_thread:
es = db_models.FlavorExtraSpec(
key_name_value=series_metadata['es_thread_policy'])
extra_spec_needed.append(es)
requested_options.extend(option_thread)
# Evalulate mixed options
assorted_opts = []
for mixed_key, mixed_value in mixed_options.items():
assorted_opts = [z for z in mixed_key.split('_')]
mixed_present = True
for opt in assorted_opts:
mixed_present &= True if opt in requested_options else False
if mixed_present:
es = db_models.FlavorExtraSpec(key_name_value=mixed_value)
extra_spec_needed.append(es)
# convert the key_value to a string to avoid/fix pecan json
# rendering error in update extra_specs
i = 0
while i < len(extra_spec_needed):
extra_spec_needed[i].key_value = str(
extra_spec_needed[i].key_value)
i += 1
return extra_spec_needed
def get_as_summary_dict(self):
return dict(
id=self.id,
name=self.name,
description=self.description
)
def handle_region_groups(self):
regions_to_add = []
# get copy of it to be able to delete from the origin
for region in self.regions[:]:
if region.type == "group":
group_regions = self.get_regions_for_group(region.name)
if group_regions is None:
raise ValueError("Group {} not found".format(region.name))
for group_region in group_regions:
regions_to_add.append(Region(name=group_region,
type='single'))
self.regions.remove(region)
# remove duplicates if exist
self.regions.extend(set(regions_to_add))
def get_regions_for_group(self, group_name):
set_utils_conf(conf)
regions = get_regions_of_group(group_name)
return regions
def validInt(self, check_value):
try:
int(check_value)
except ValueError:
return False
return True
class FlavorWrapper(Model):
"""flavor model
"""
flavor = wsme.wsattr(Flavor, mandatory=True, name='flavor')
def __init__(self, flavor=Flavor()):
"""init
:param flavor: flavor dict
"""
self.flavor = flavor
def to_db_model(self):
return self.flavor.to_db_model()
def validate_model(self):
return self.flavor.validate_model()
def get_extra_spec_needed(self):
return self.flavor.get_extra_spec_needed()
@staticmethod
def from_db_model(sql_flavor):
flavors = FlavorWrapper()
flavors.flavor = Flavor.from_db_model(sql_flavor)
return flavors
'''
' FlavorSummary a DataObject contains all the fields defined in FlavorSummary.
'''
class FlavorSummary(Model):
name = wsme.wsattr(wsme.types.text)
id = wsme.wsattr(wsme.types.text)
description = wsme.wsattr(wsme.types.text)
visibility = wsme.wsattr(wsme.types.text)
def __init__(self, name='', id='', description='', visibility=''):
Model.__init__(self)
self.name = name
self.id = id
self.description = description
self.visibility = visibility
@staticmethod
def from_db_model(sql_flavor):
flavor = FlavorSummary()
flavor.id = sql_flavor.id
flavor.name = sql_flavor.name
flavor.description = sql_flavor.description
flavor.visibility = sql_flavor.visibility
return flavor
class FlavorSummaryResponse(Model):
flavors = wsme.wsattr([FlavorSummary], mandatory=True)
def __init__(self): # pragma: no cover
Model.__init__(self)
self.flavors = []
class FlavorListFullResponse(Model):
flavors = wsme.wsattr([Flavor], mandatory=True)
def __init__(self): # pragma: no cover
Model.__init__(self)
self.flavors = []