Fixing DB issue with auto-generation of flavors

- Inlcude keystone authentication
 - Updated and included unit tests

Change-Id: I8ad4790db585b274293ac34e7d0ba7ec00f61df1
Signed-off-by: Helena McGough <helena.mcgough@intel.com>
This commit is contained in:
Helena McGough 2019-03-29 14:37:47 +00:00
parent 97ab8e835e
commit cb6f9fae17
12 changed files with 673 additions and 202 deletions

View File

@ -4,4 +4,3 @@
- openstack-python36-jobs
- check-requirements
- openstack-cover-jobs
- openstack-lower-constraints-jobs

View File

@ -35,6 +35,11 @@ function configure_nova_rsd {
iniset $NOVA_CONF rsd podm_user ${PODM_USER}
iniset $NOVA_CONF rsd podm_password ${PODM_PASSWD}
iniset $NOVA_CONF rsd podm_port ${PODM_PORT}
iniset $NOVA_CONF rsd auth_password ${OS_PASSWORD}
iniset $NOVA_CONF rsd auth_url ${OS_AUTH_URL}
iniset $NOVA_CONF rsd identity_version ${OS_IDENTITY_API_VERSION}
iniset $NOVA_CONF rsd tenant_name ${OS_PROJECT_NAME}
iniset $NOVA_CONF rsd username ${OS_USERNAME}
}
# disabling ERROR_NO_CLONE to allow this plugin work with devstack-gate

View File

@ -1,6 +1,10 @@
# This driver is enabled in override-defaults with:
# VIRT_DRIVER=${VIRT_DRIVER:-rsd}
# Auth info
OS_AUTH_URL="$KEYSTONE_SERVICE_URI/v$IDENTITY_API_VERSION"
OS_IDENTITY_API_VERSION=${IDENTITY_API_VERSION:-3}
if [ "$VERBOSE" == "False" ]; then
# allow local debugging
set -o xtrace

View File

@ -0,0 +1,145 @@
# -*- coding: utf-8 -*-
# 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.
""" Lightweight (keystone) client for the OpenStack Identity API """
import logging
import requests
LOG = logging.getLogger(__name__)
class KeystoneException(Exception):
def __init__(self, message, exc=None, response=None):
if exc:
message += "\nReason: %s" % exc
super(KeystoneException, self).__init__(message)
self.response = response
self.exception = exc
class InvalidResponse(KeystoneException):
def __init__(self, exc, response):
super(InvalidResponse, self).__init__(
"Invalid response from ident", exc, response)
class MissingServices(KeystoneException):
def __init__(self, message, exc, response):
super(MissingServices, self).__init__(
"MissingServices: " + message, exc, response)
class ClientV3(object):
"""Light weight client for the OpenStack Identity API V3.
:param string username: Username for authentication.
:param string password: Password for authentication.
:param string tenant_name: Tenant name.
:param string auth_url: Keystone service endpoint for authorization.
"""
def __init__(self, auth_url, username, password, tenant_name):
"""Initialize a new client"""
self.auth_url = auth_url
self.username = username
self.password = password
self.tenant_name = tenant_name
self._auth_token = ''
self._services = ()
self._services_by_name = {}
@property
def auth_token(self):
"""Return token string usable for X-Auth-Token """
# actualize token
self.refresh()
return self._auth_token
@property
def services(self):
"""Return list of services retrieved from identity server """
return self._services
def refresh(self):
"""Refresh token and services list."""
headers = {'Accept': 'application/json'}
url = self.auth_url.rstrip('/') + '/auth/tokens'
params = {
'auth': {
'identity': {
'methods': ['password'],
'password': {
'user': {
'name': self.username,
'domain': {'id': 'default'},
'password': self.password
}
}
},
'scope': {
'project': {
'name': self.tenant_name,
'domain': {'id': 'default'}
}
}
}
}
resp = requests.post(url, json=params, headers=headers)
resp_data = None
# processing response
try:
resp.raise_for_status()
resp_data = resp.json()['token']
self._services = tuple(resp_data['catalog'])
self._services_by_name = {
service['name']: service for service in self._services
}
self._auth_token = resp.headers['X-Subject-Token']
except (TypeError, KeyError, ValueError,
requests.exceptions.HTTPError) as e:
LOG.exception("Error processing response from keystone")
raise InvalidResponse(e, resp_data)
return resp_data
def get_service_endpoint(self, name, urlkey="public", region=None):
"""Return url endpoint of service
possible values of urlkey = 'adminURL' | 'publicURL' | 'internalURL'
provide region if more endpoints are available
"""
try:
endpoints = self._services_by_name[name]['endpoints']
if not endpoints:
raise MissingServices("Missing name '%s' in received services"
% name,
None, self._services)
if region:
for ep in endpoints:
if ep['region'] == region and ep['interface'] in urlkey:
return ep["url"].rstrip('/')
else:
for ep in endpoints:
if ep['interface'] in urlkey:
return ep["url"].rstrip('/')
raise MissingServices("No valid endpoints found")
except (KeyError, ValueError) as e:
LOG.exception("Error while processing endpoints")
raise MissingServices("Missing data in received services",
e, self._services)

View File

@ -39,7 +39,22 @@ rsd_opts = [
'PODM. '),
cfg.IntOpt('podm_port',
default=8443,
help='Specifying port on PODM for communication. ')
help='Specifying port on PODM for communication. '),
cfg.StrOpt('auth_password',
default='',
help='Password required to authenticate to keystone. '),
cfg.StrOpt('auth_url',
default='',
help='URL require to authenticate to keystone. '),
cfg.IntOpt('identity_version',
default=3,
help='Keystone version. '),
cfg.StrOpt('tenant_name',
default='',
help='Name of the openstack tenant. '),
cfg.StrOpt('username',
default='',
help='OpenStack username to authenticate to keystone. ')
]
STATIC_OPTIONS = (rsd_opts)

View File

@ -40,3 +40,13 @@ class TestConf(test.NoDBTestCase):
self.assertEqual('admin', CONF.rsd.podm_password)
# PODM port
self.assertEqual(8443, CONF.rsd.podm_port)
# auth password
self.assertEqual('', CONF.rsd.auth_password)
# auth url
self.assertEqual('', CONF.rsd.auth_url)
# keystone version
self.assertEqual(3, CONF.rsd.identity_version)
# Tenant Name
self.assertEqual('', CONF.rsd.tenant_name)
# Auth username
self.assertEqual('', CONF.rsd.username)

View File

@ -0,0 +1,211 @@
# 2017 - 2018 Intel Corporation. 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.
"""Tests for the RSD keystone_light authentication."""
import mock
import requests
from nova import test
from rsd_virt_for_nova.conf.keystone_light import ClientV3
from rsd_virt_for_nova.conf.keystone_light import MissingServices
class TestClientV3(test.NoDBTestCase):
"""Test class for configurations."""
def setUp(self):
"""Initialize configuration test class."""
super(TestClientV3, self).setUp()
self.client = ClientV3("my_auth_url", "user", "pass", "tenant")
self.test_authtoken = "c5bbb1c9a27e470fb482de2a718e08c2"
self.test_public_endpoint = "http://public_endpoint"
self.test_internal_endpoint = "http://internal_endpoint"
self.test_region = "RegionOne"
response = {"token": {
"is_domain": 'false',
"methods": [
"password"
],
"roles": [
{
"id": "eacf519eb1264cba9ad645355ce1f6ec",
"name": "ResellerAdmin"
},
{
"id": "63e481b5d5f545ecb8947072ff34f10d",
"name": "admin"
}
],
"is_admin_project": 'false',
"project": {
"domain": {
"id": "default",
"name": "Default"
},
"id": "97467f21efb2493c92481429a04df7bd",
"name": "service"
},
"catalog": [
{
"endpoints": [
{
"url": self.test_public_endpoint + '/',
"interface": "public",
"region": self.test_region,
"region_id": self.test_region,
"id": "5e1d9a45d7d442ca8971a5112b2e89b5"
},
{
"url": "http://127.0.0.1:8777",
"interface": "admin",
"region": self.test_region,
"region_id": self.test_region,
"id": "5e8b536fde6049d381ee540c018905d1"
},
{
"url": self.test_internal_endpoint + '/',
"interface": "internal",
"region": self.test_region,
"region_id": self.test_region,
"id": "db90c733ddd9466696bc5aaec43b18d0"
}
],
"type": "compute",
"id": "f6c15a041d574bc190c70815a14ab851",
"name": "nova"
}
]
}
}
self.mock_response = mock.Mock()
self.mock_response.json.return_value = response
self.mock_response.headers = {
'X-Subject-Token': "c5bbb1c9a27e470fb482de2a718e08c2"
}
def test_ClientV3_init(self):
"""Test initialising keystone clientv3."""
self.assertEqual(self.client.auth_url, "my_auth_url")
self.assertEqual(self.client.username, "user")
self.assertEqual(self.client.password, "pass")
self.assertEqual(self.client.tenant_name, "tenant")
self.assertEqual(self.client._auth_token, '')
self.assertEqual(self.client._services, ())
self.assertEqual(self.client._services_by_name, {})
@mock.patch.object(requests, "post")
def test_refresh(self, post_req):
"""Test the refresh function for auth tokens."""
resp = self.client.refresh()
url = self.client.auth_url.rstrip('/') + '/auth/tokens'
params = {
'auth': {
'identity': {
'methods': ['password'],
'password': {
'user': {
'name': self.client.username,
'domain': {'id': 'default'},
'password': self.client.password
}
}
},
'scope': {
'project': {
'name': self.client.tenant_name,
'domain': {'id': 'default'}
}
}
}
}
headers = {'Accept': 'application/json'}
post_req.assert_called_once_with(url, json=params, headers=headers)
self.assertEqual(resp, post_req.return_value.json()['token'])
@mock.patch.object(requests, 'post')
def test_getservice_endpoint(self, mock_post):
"""Test get_service_endpoint"""
mock_post.return_value = self.mock_response
client = ClientV3("test_auth_url", "test_username",
"test_password", "test_tenant")
client.refresh()
endpoint = client.get_service_endpoint('nova')
self.assertEqual(endpoint, self.test_public_endpoint)
self.assertRaises(MissingServices,
client.get_service_endpoint, 'badname')
@mock.patch.object(requests, 'post')
def test_getservice_endpoint_error(self, mock_post):
"""Test get service endpoint error"""
response = {"token": {
"is_domain": 'false',
"methods": [
"password"
],
"roles": [
{
"id": "eacf519eb1264cba9ad645355ce1f6ec",
"name": "ResellerAdmin"
},
{
"id": "63e481b5d5f545ecb8947072ff34f10d",
"name": "admin"
}
],
"is_admin_project": 'false',
"project": {
"domain": {
"id": "default",
"name": "Default"
},
"id": "97467f21efb2493c92481429a04df7bd",
"name": "service"
},
"catalog": [
{
"endpoints": [],
"type": "compute",
"id": "f6c15a041d574bc190c70815a14ab851",
"name": "badname"
}
]
}
}
self.mock_response = mock.Mock()
self.mock_response.json.return_value = response
self.mock_response.headers = {
'X-Subject-Token': "c5bbb1c9a27e470fb482de2a718e08c2"
}
mock_post.return_value = self.mock_response
client = ClientV3("test_auth_url", "test_username",
"test_password", "test_tenant")
client.refresh()
self.assertRaises(MissingServices, client.get_service_endpoint, 'nova')

View File

@ -28,13 +28,9 @@ from nova import rc_fields as fields
from nova.compute import power_state
from nova.compute import provider_tree
from nova.objects import flavor
from nova.virt import fake
from nova.virt import hardware
from rsd_virt_for_nova.conf import rsd as cfg
from rsd_virt_for_nova.virt import rsd
from rsd_virt_for_nova.virt.rsd import driver
@ -54,8 +50,6 @@ from rsd_lib.resources.v2_3.node import node as v2_3_node
from sushy import connector
CONF = cfg.CONF
class FakeInstance(object):
"""A class to fake out nova instances."""
@ -703,108 +697,3 @@ class TestRSDDriver(base.BaseTestCase):
'/redfish/v1/Systems/System2',
'/redfish/v1/Systems/System3',
'/redfish/v1/Systems/System4'])
@mock.patch.object(driver.RSDDriver, 'check_chassis_systems')
@mock.patch.object(context, 'get_admin_context')
@mock.patch.object(flavor.Flavor, '_flavor_get_by_flavor_id_from_db')
@mock.patch.object(flavor, '_flavor_create')
@mock.patch.object(fields.ResourceClass, 'normalize_name')
@mock.patch.object(driver.RSDDriver, 'conv_GiB_to_MiB')
def test_create_flavors_success(self, conv_mem, norm_name, flav_create,
get_flav, admin_context, check_chas):
"""Test creation of new flavors for a System, success."""
# Set up the mock objects for a sucessful creation test
sys_col = self.RSD.driver.PODM.get_system_collection.return_value
chas_col = self.RSD.driver.PODM.get_chassis_collection.return_value
chas_col.members_identities = ['/redfish/v1/Chassis/Chassis1']
sys_col.members_identities = ['/redfish/v1/Systems/System1']
sys_col.get_member.return_value = self.system_inst
chas_col.get_member.return_value = self.chassis_inst
check_chas.return_value = ['/redfish/v1/Systems/System1']
spec = 'resources:' + norm_name.return_value
mem = conv_mem.return_value - 512
proc = self.system_inst.processors.summary.count
flav_id = str(mem) + 'MB-' + str(proc) + 'vcpus'
# Run test
self.RSD._create_flavors()
# Check the function calls for the test
self.RSD.driver.PODM.get_system_collection.assert_called_once()
self.RSD.driver.PODM.get_chassis_collection.assert_called()
chas_col.get_member.assert_called_with('/redfish/v1/Chassis/Chassis1')
check_chas.assert_called()
sys_col.get_member.assert_called_with('/redfish/v1/Systems/System1')
conv_mem.assert_called_with(self.system_inst.memory_summary.size_gib)
norm_name.assert_called_with(flav_id)
admin_context.assert_called()
# Flavor creation call check
flav_create.assert_called_once_with(
admin_context.return_value,
{'name': 'RSD-' + flav_id,
'flavorid': flav_id,
'memory_mb': mem,
'vcpus': self.system_inst.processors.summary.count,
'root_gb': 0,
'extra_specs': {spec: '1'}})
get_flav.assert_not_called()
@mock.patch.object(flavor.Flavor, '_flavor_get_by_flavor_id_from_db')
@mock.patch.object(flavor, '_flavor_create')
@mock.patch.object(fields.ResourceClass, 'normalize_name')
@mock.patch.object(driver.RSDDriver, 'conv_GiB_to_MiB')
def test_create_flavors_failure(self, conv_mem, norm_name, flav_create,
flav_from_db):
"""Test failing the creation of new flavors for a System."""
# Setup for a failed flavor creation test
sys_col = self.RSD.driver.PODM.get_system_collection.return_value
self.RSD._create_flavors()
# Verification of flavor creation failure and invalid function calls
self.RSD.driver.PODM.get_system_collection.assert_called_once()
sys_col.get_member.assert_not_called()
conv_mem.assert_not_called()
norm_name.assert_not_called()
flav_create.assert_not_called()
flav_from_db.assert_not_called()
@mock.patch.object(objects.FlavorList, 'get_all')
@mock.patch.object(context, 'get_admin_context')
@mock.patch.object(flavor, '_flavor_destroy')
def test_check_flavors_failure(self, flav_destroy, get_context, flav_list):
"""Test for failing to check existing flavors."""
# Setup for the test to fail to check flavors
sys_col = self.RSD.driver.PODM.get_system_collection.return_value
self.RSD.check_flavors(self.system_col, [])
# Confirm that checking the available flavors failed
get_context.assert_called()
flav_list.assert_called_with(get_context.return_value)
sys_col.get_member.assert_not_called()
flav_destroy.assert_not_called()
@mock.patch.object(objects.FlavorList, 'get_all')
@mock.patch.object(context, 'get_admin_context')
@mock.patch.object(flavor, '_flavor_destroy')
def test_check_flavors_success(self, flav_destroy, get_context, flav_list):
"""Test for successfully checking existing flavors."""
# Setup for check flavor that exists
sys_col = self.RSD.driver.PODM.get_system_collection.return_value
sys_col.get_member.return_value = self.system_inst
self.RSD.rsd_flavors = {
'mock_flav_id': {
'id': 'flav_id',
'rsd_systems': {
'/redfish/v1/Chassis/Chassis1':
self.system_inst.identity
}
}
}
self.RSD.check_flavors(sys_col, ['/redfish/v1/Systems/System1'])
# Confirm the list of available flavors
# No flavors need to be deleted
get_context.assert_called()
flav_list.assert_called_with(get_context.return_value)
sys_col.get_member.assert_called_with('/redfish/v1/Systems/System1')
flav_destroy.assert_called()

View File

@ -0,0 +1,108 @@
# -*- coding: utf-8 -*-
# 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.
"""Unit tests for the RSD flavor management class."""
import mock
from rsd_virt_for_nova.conf import rsd as cfg
from rsd_virt_for_nova.virt.rsd import flavor_management
from rsd_virt_for_nova.conf import keystone_light
from oslotest import base
CONF = cfg.CONF
class TestFlavorManager(base.BaseTestCase):
"""A test class for the flavor manager class."""
def setUp(self):
"""Initial setup of mocks for all of the unit tests."""
super(TestFlavorManager, self).setUp()
self.flav_man = flavor_management.FlavorManager()
def test_init(self):
"""Test the initialisation of a flavor manager instance."""
self.assertEqual(self.flav_man._url_base, None)
self.assertEqual(self.flav_man._keystone, None)
self.assertEqual(self.flav_man._auth_token, None)
self.assertEqual(self.flav_man.headers, None)
def test_keystone_req(self):
"""Test a successful keystone request."""
# TODO(helenam100): write successful and failed versions of test
@mock.patch.object(flavor_management.FlavorManager, "_get_endpoint")
def test_get_base_url(self, get_endpoint):
"""Test authentication functionality."""
url = self.flav_man._get_base_url()
get_endpoint.assert_called_once_with("nova")
self.assertEqual(self.flav_man._url_base,
"{}/flavors".format(get_endpoint.return_value))
self.assertEqual(url, self.flav_man._url_base)
@mock.patch.object(keystone_light, "ClientV3")
def test_get_endpoint_success(self, client):
"""Test getting a valid endpoint for flavor creation."""
self.flav_man._keystone = client.return_value
endpoint = self.flav_man._get_endpoint("nova")
self.flav_man._keystone.get_service_endpoint.assert_called_with("nova")
self.assertEqual(endpoint,
self.flav_man._keystone.get_service_endpoint.return_value)
@mock.patch.object(keystone_light.ClientV3, "get_service_endpoint")
def test_get_endpoint_failure(self, serv_endpoint):
"""Failed test for getting an endpoint for flavor create."""
# No valid keystone test
self.assertRaises(AttributeError, self.flav_man._get_endpoint, "nova")
@mock.patch.object(flavor_management.FlavorManager, "_get_endpoint")
def test_create_request_url_delete(self, get_end):
"""Testing creation of a request url for flavor management."""
url = self.flav_man._create_request_url("flav_id", "delete")
get_end.assert_called_once_with("nova")
self.assertEquals(url,
"{}/flavors/flav_id".format(get_end.return_value))
@mock.patch.object(flavor_management.FlavorManager, "_get_endpoint")
def test_create_request_url_update(self, get_end):
"""Testing creation of a request url for flavor management."""
url = self.flav_man._create_request_url("flav_id", "update")
get_end.assert_called_once_with("nova")
self.assertEquals(url,
"{}/flavors/flav_id/os-extra_specs".format(
get_end.return_value))
@mock.patch.object(flavor_management.FlavorManager, "_get_endpoint")
def test_create_request_url_invalid(self, get_end):
"""Testing creation of a request url for flavor management."""
url = self.flav_man._create_request_url("flav_id", "invalid")
get_end.assert_called_once_with("nova")
self.assertEquals(url, '')
def test_get_headers(self):
"""Testing getting headers for requests."""
headers = self.flav_man.get_headers("my_auth_token")
self.assertEquals(headers, self.flav_man.headers)
self.assertEquals(headers,
{'X-Auth-Token': "my_auth_token",
'Content-type': 'application/json'})

View File

@ -23,31 +23,26 @@ import copy
import json
import requests
from collections import OrderedDict
from nova import context
from nova import exception
from nova import objects
from nova import rc_fields as fields
from nova.compute import power_state
from nova.objects import fields as obj_fields
from nova.objects import flavor
from nova.virt import driver
from nova.virt import hardware
from rsd_virt_for_nova.conf import rsd as cfg
from rsd_virt_for_nova.virt import rsd
from rsd_virt_for_nova.virt.rsd import flavor_management
from oslo_log import log as logging
from oslo_utils import versionutils
CONF = cfg.CONF
LOG = logging.getLogger(__name__)
PODM_NODE = ()
@ -72,6 +67,7 @@ class RSDDriver(driver.ComputeDriver):
"""Initialize the RSDDriver."""
super(RSDDriver, self).__init__(virtapi)
# Initializes vairables to track compute nodes and instances
self.flavor_manager = flavor_management.FlavorManager()
self.driver = rsd.PODM_connection()
self.instances = OrderedDict()
self.rsd_flavors = OrderedDict()
@ -86,21 +82,16 @@ class RSDDriver(driver.ComputeDriver):
nodes = []
CHASSIS_COL = self.driver.PODM.get_chassis_collection()
for c in CHASSIS_COL.members_identities:
try:
chas = CHASSIS_COL.get_member(c)
cha_sys = self.check_chassis_systems(chas)
if cha_sys != []:
nodes.append(c)
except Exception as c_ex:
LOG.warn("Failed to get chassis information: %s", c_ex)
nodes = []
chas = CHASSIS_COL.get_member(c)
cha_sys = self.check_chassis_systems(chas)
if cha_sys != []:
nodes.append(c)
set_nodes(nodes)
return copy.copy(PODM_NODE)
def init_host(self, host):
"""Initialize anything that is necessary for the driver to function."""
self._nodes = self._init_nodes()
return host
def get_info(self, instance):
@ -197,15 +188,15 @@ class RSDDriver(driver.ComputeDriver):
cpu_info = ''
cha_sys = []
if nodename not in self._nodes:
return {}
SYSTEM_COL = self.driver.PODM.get_system_collection()
members = SYSTEM_COL.members_identities
CHASSIS_COL = self.driver.PODM.get_chassis_collection()
try:
chas = CHASSIS_COL.get_member(nodename)
cha_sys = self.check_chassis_systems(chas)
except Exception as ex:
LOG.warn("Failed to retrieve chassis information:%s", ex)
chas = CHASSIS_COL.get_member(nodename)
cha_sys = self.check_chassis_systems(chas)
# Check if all flavors are valid
self.check_flavors(SYSTEM_COL, members)
@ -447,55 +438,57 @@ class RSDDriver(driver.ComputeDriver):
proc = sys.processors.summary.count
flav_id = str(mem) + 'MB-' + str(proc) + 'vcpus'
res = fields.ResourceClass.normalize_name(flav_id)
spec = 'resources:' + res
values = {
'name': 'RSD-' + flav_id,
'flavorid': flav_id,
'memory_mb': mem,
'vcpus': proc,
'root_gb': 0,
'extra_specs': {
spec: '1'}
spec = str('resources:' + res)
payload = {
"flavor": {
'name': 'RSD-' + flav_id,
'id': flav_id,
'ram': mem,
'vcpus': proc,
'disk': 0
}
}
if sys.identity not in self.rsd_flavors:
if flav_id not in self.rsd_flavors.keys():
try:
LOG.debug("New flavor for system: %s", sys.identity)
flavor._flavor_create(
context.get_admin_context(), values)
self.chas_systems[str(chas.path)] = [str(sys.identity)]
requests.post(
self._url_base, data=json.dumps(payload),
headers=self.headers)
except Exception as ex:
LOG.warn("Failed to create the new flavor: %s", ex)
try:
extra_pay = {
'extra_specs': {
spec: '1'}
}
update_url = self.flavor_manager._create_request_url(
flav_id, 'update')
requests.post(
update_url, data=json.dumps(extra_pay),
headers=self.headers)
self.rsd_flavors[flav_id] = {
'id': flav_id,
'rsd_systems': {
str(chas.path): str(sys.identity)}
}
except Exception as ex:
LOG.warn("Failed to add extra_specs:%s", ex)
else:
chassis_ = self.rsd_flavors[flav_id]['rsd_systems']
if str(chas.path) not in chassis_.keys():
self.chas_systems[str(chas.path)] = [
str(sys.identity)]
self.rsd_flavors[flav_id] = {
'rsd_systems': self.chas_systems
}
self._nodes = self._init_nodes()
except Exception as ex:
LOG.debug(
"A flavor already exists for this system: %s", ex)
flavor.Flavor._flavor_get_by_flavor_id_from_db(
context.get_admin_context(), flav_id)
if flav_id not in self.rsd_flavors.keys():
self.chas_systems[str(chas.path)] = [
str(sys.identity)]
}
else:
systems = self.rsd_flavors[
flav_id]['rsd_systems'][str(chas.path)]
if str(sys.identity) not in systems:
systems.append(str(sys.identity))
self.chas_systems[str(chas.path)] = systems
self.rsd_flavors[flav_id] = {
'rsd_systems': self.chas_systems
}
else:
chassis_ = self.rsd_flavors[flav_id]['rsd_systems']
if str(chas.path) not in chassis_.keys():
self.chas_systems[str(chas.path)] = [
str(sys.identity)]
self.rsd_flavors[flav_id] = {
'rsd_systems': self.chas_systems
}
else:
systems = self.rsd_flavors[
flav_id]['rsd_systems'][str(chas.path)]
if str(sys.identity) not in systems:
systems.append(str(sys.identity))
self.chas_systems[str(chas.path)] = systems
self.rsd_flavors[flav_id] = {
'rsd_systems': self.chas_systems
}
def check_flavors(self, collection, systems):
"""Check if flavors should be deleted based on system removal."""
@ -510,30 +503,34 @@ class RSDDriver(driver.ComputeDriver):
flav_id = str(mem) + 'MB-' + str(proc) + 'vcpus'
flav_ids.append(flav_id)
f_list = objects.FlavorList.get_all(context.get_admin_context())
self._keystone = self.flavor_manager.keystone_req()
self._auth_token = self._keystone.auth_token
self._url_base = self.flavor_manager._get_base_url()
self.headers = self.flavor_manager.get_headers(self._auth_token)
req = requests.get(self._url_base, headers=self.headers)
f_list = json.loads(req.text)['flavors']
for f in f_list:
if 'RSD' in f.name:
if f.flavorid not in flav_ids:
try:
flavor._flavor_destroy(
context.get_admin_context(),
flavor_id=f.flavorid)
except exception.FlavorNotFound as ex:
LOG.warn("Flavor not found exception: %s", ex)
if 'RSD' in f['name']:
if f['id'] not in flav_ids:
del_url = self.flavor_manager._create_request_url(
f['id'], 'delete')
fla_del = requests.delete(del_url, headers=self.headers)
for k in list(self.rsd_flavors):
if k in self.rsd_flavors.keys():
chas_list = self.rsd_flavors[k]['rsd_systems'].keys()
for c in chas_list:
sys_list = self.rsd_flavors[k]['rsd_systems'][c]
for s in sys_list:
if s not in sys_ids:
try:
rsd_id = self.rsd_flavors[k]['id']
flavor._flavor_destroy(
context.get_admin_context(), rsd_id)
LOG.debug("Deleting flavor: %s", k)
del self.rsd_flavors[k]
except KeyError as k_ex:
LOG.warn("Flavor doesn't exist:%s", k_ex)
sys_list = self.rsd_flavors[k]['rsd_systems'].values()
for s in sys_list:
if s not in sys_ids:
rsd_id = self.rsd_flavors[k]['id']
del_url = self.flavor_manager._create_request_url(
rsd_id, 'delete')
try:
LOG.debug("Deleting flavor for removed systems: %s", k)
fla_del = requests.delete(del_url,
headers=self.headers)
del self.rsd_flavors[k]
except Exception as ex:
LOG.warn("Failed to delete flavor: %s, %s",
json.loads(fla_del.text), ex)
return

View File

@ -0,0 +1,88 @@
# -*- coding: utf-8 -*-
# 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.
"""
A class + functions for managing RSD flavors
Requires the authentication to keystone to perform request to the nova-api's.
This allows the management and creation of RSD specific flavors.
"""
from rsd_virt_for_nova.conf import rsd as cfg
from rsd_virt_for_nova.conf.keystone_light import ClientV3
from oslo_log import log as logging
CONF = cfg.CONF
LOG = logging.getLogger(__name__)
class FlavorManager(object):
"""Implementation of nova compute driver to compose RSD nodes from nova."""
def __init__(self):
"""Initialize the RSDDriver."""
self._url_base = None
self._keystone = None
self._auth_token = None
self.headers = None
def keystone_req(self):
"""Authenticate to keystone."""
keystone_url = ''
OS_USERNAME = CONF.rsd.username
OS_PASSWORD = CONF.rsd.auth_password
OS_TENANT_NAME = CONF.rsd.tenant_name
OS_AUTH_URL = CONF.rsd.auth_url
OS_IDENTITY_VERSION = CONF.rsd.identity_version
keystone_url = OS_AUTH_URL + '/v' + str(OS_IDENTITY_VERSION)
self._keystone = ClientV3(
auth_url=str(keystone_url),
username=OS_USERNAME,
password=OS_PASSWORD,
tenant_name=OS_TENANT_NAME
)
self._auth_token = self._keystone.auth_token
return self._keystone
def _get_base_url(self):
# get the uri of service endpoint
endpoint = self._get_endpoint("nova")
self._url_base = "{}/flavors".format(endpoint)
return self._url_base
def _get_endpoint(self, service):
# get the uri of service endpoint
endpoint = self._keystone.get_service_endpoint(service)
return endpoint
def _create_request_url(self, flavorid, req_type):
endpoint = self._get_endpoint("nova")
url = ''
if req_type == 'delete':
url = "{}/flavors/%s".format(endpoint) % (flavorid)
elif req_type == 'update':
url = "{}/flavors/%s/os-extra_specs".format(endpoint) % (flavorid)
return url
def get_headers(self, auth_token):
self.headers = {'X-Auth-Token': auth_token,
'Content-type': 'application/json'}
return self.headers

View File

@ -5,7 +5,7 @@
[tox]
minversion = 2.0
envlist = py27,py36,pycodestyle
envlist = py27,py36,pep8
skipsdist = True
[testenv]
@ -23,7 +23,7 @@ deps = -r{toxinidir}/requirements.txt
-r{toxinidir}/test-requirements.txt
commands = stestr run {posargs}
[testenv:pycodestyle]
[testenv:pep8]
commands =
flake8 {posargs}
pycodestyle {posargs}