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 - openstack-python36-jobs
- check-requirements - check-requirements
- openstack-cover-jobs - 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_user ${PODM_USER}
iniset $NOVA_CONF rsd podm_password ${PODM_PASSWD} iniset $NOVA_CONF rsd podm_password ${PODM_PASSWD}
iniset $NOVA_CONF rsd podm_port ${PODM_PORT} 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 # 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: # This driver is enabled in override-defaults with:
# VIRT_DRIVER=${VIRT_DRIVER:-rsd} # 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 if [ "$VERBOSE" == "False" ]; then
# allow local debugging # allow local debugging
set -o xtrace 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. '), 'PODM. '),
cfg.IntOpt('podm_port', cfg.IntOpt('podm_port',
default=8443, 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) STATIC_OPTIONS = (rsd_opts)

View File

@ -40,3 +40,13 @@ class TestConf(test.NoDBTestCase):
self.assertEqual('admin', CONF.rsd.podm_password) self.assertEqual('admin', CONF.rsd.podm_password)
# PODM port # PODM port
self.assertEqual(8443, CONF.rsd.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 power_state
from nova.compute import provider_tree from nova.compute import provider_tree
from nova.objects import flavor
from nova.virt import fake from nova.virt import fake
from nova.virt import hardware 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 import rsd
from rsd_virt_for_nova.virt.rsd import driver 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 from sushy import connector
CONF = cfg.CONF
class FakeInstance(object): class FakeInstance(object):
"""A class to fake out nova instances.""" """A class to fake out nova instances."""
@ -703,108 +697,3 @@ class TestRSDDriver(base.BaseTestCase):
'/redfish/v1/Systems/System2', '/redfish/v1/Systems/System2',
'/redfish/v1/Systems/System3', '/redfish/v1/Systems/System3',
'/redfish/v1/Systems/System4']) '/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 json
import requests
from collections import OrderedDict from collections import OrderedDict
from nova import context
from nova import exception from nova import exception
from nova import objects
from nova import rc_fields as fields from nova import rc_fields as fields
from nova.compute import power_state from nova.compute import power_state
from nova.objects import fields as obj_fields from nova.objects import fields as obj_fields
from nova.objects import flavor
from nova.virt import driver from nova.virt import driver
from nova.virt import hardware 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 import rsd
from rsd_virt_for_nova.virt.rsd import flavor_management
from oslo_log import log as logging from oslo_log import log as logging
from oslo_utils import versionutils from oslo_utils import versionutils
CONF = cfg.CONF
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
PODM_NODE = () PODM_NODE = ()
@ -72,6 +67,7 @@ class RSDDriver(driver.ComputeDriver):
"""Initialize the RSDDriver.""" """Initialize the RSDDriver."""
super(RSDDriver, self).__init__(virtapi) super(RSDDriver, self).__init__(virtapi)
# Initializes vairables to track compute nodes and instances # Initializes vairables to track compute nodes and instances
self.flavor_manager = flavor_management.FlavorManager()
self.driver = rsd.PODM_connection() self.driver = rsd.PODM_connection()
self.instances = OrderedDict() self.instances = OrderedDict()
self.rsd_flavors = OrderedDict() self.rsd_flavors = OrderedDict()
@ -86,21 +82,16 @@ class RSDDriver(driver.ComputeDriver):
nodes = [] nodes = []
CHASSIS_COL = self.driver.PODM.get_chassis_collection() CHASSIS_COL = self.driver.PODM.get_chassis_collection()
for c in CHASSIS_COL.members_identities: for c in CHASSIS_COL.members_identities:
try:
chas = CHASSIS_COL.get_member(c) chas = CHASSIS_COL.get_member(c)
cha_sys = self.check_chassis_systems(chas) cha_sys = self.check_chassis_systems(chas)
if cha_sys != []: if cha_sys != []:
nodes.append(c) nodes.append(c)
except Exception as c_ex:
LOG.warn("Failed to get chassis information: %s", c_ex)
nodes = []
set_nodes(nodes) set_nodes(nodes)
return copy.copy(PODM_NODE) return copy.copy(PODM_NODE)
def init_host(self, host): def init_host(self, host):
"""Initialize anything that is necessary for the driver to function.""" """Initialize anything that is necessary for the driver to function."""
self._nodes = self._init_nodes()
return host return host
def get_info(self, instance): def get_info(self, instance):
@ -197,15 +188,15 @@ class RSDDriver(driver.ComputeDriver):
cpu_info = '' cpu_info = ''
cha_sys = [] cha_sys = []
if nodename not in self._nodes:
return {}
SYSTEM_COL = self.driver.PODM.get_system_collection() SYSTEM_COL = self.driver.PODM.get_system_collection()
members = SYSTEM_COL.members_identities members = SYSTEM_COL.members_identities
CHASSIS_COL = self.driver.PODM.get_chassis_collection() CHASSIS_COL = self.driver.PODM.get_chassis_collection()
try:
chas = CHASSIS_COL.get_member(nodename) chas = CHASSIS_COL.get_member(nodename)
cha_sys = self.check_chassis_systems(chas) cha_sys = self.check_chassis_systems(chas)
except Exception as ex:
LOG.warn("Failed to retrieve chassis information:%s", ex)
# Check if all flavors are valid # Check if all flavors are valid
self.check_flavors(SYSTEM_COL, members) self.check_flavors(SYSTEM_COL, members)
@ -447,38 +438,40 @@ class RSDDriver(driver.ComputeDriver):
proc = sys.processors.summary.count proc = sys.processors.summary.count
flav_id = str(mem) + 'MB-' + str(proc) + 'vcpus' flav_id = str(mem) + 'MB-' + str(proc) + 'vcpus'
res = fields.ResourceClass.normalize_name(flav_id) res = fields.ResourceClass.normalize_name(flav_id)
spec = 'resources:' + res spec = str('resources:' + res)
values = { payload = {
"flavor": {
'name': 'RSD-' + flav_id, 'name': 'RSD-' + flav_id,
'flavorid': flav_id, 'id': flav_id,
'memory_mb': mem, 'ram': mem,
'vcpus': proc, 'vcpus': proc,
'root_gb': 0, 'disk': 0
}
}
if flav_id not in self.rsd_flavors.keys():
try:
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': { 'extra_specs': {
spec: '1'} spec: '1'}
} }
if sys.identity not in self.rsd_flavors: update_url = self.flavor_manager._create_request_url(
try: flav_id, 'update')
LOG.debug("New flavor for system: %s", sys.identity) requests.post(
flavor._flavor_create( update_url, data=json.dumps(extra_pay),
context.get_admin_context(), values) headers=self.headers)
self.chas_systems[str(chas.path)] = [str(sys.identity)]
self.rsd_flavors[flav_id] = { self.rsd_flavors[flav_id] = {
'rsd_systems': self.chas_systems 'id': flav_id,
'rsd_systems': {
str(chas.path): str(sys.identity)}
} }
self._nodes = self._init_nodes()
except Exception as ex: except Exception as ex:
LOG.debug( LOG.warn("Failed to add extra_specs:%s", ex)
"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)]
self.rsd_flavors[flav_id] = {
'rsd_systems': self.chas_systems
}
else: else:
chassis_ = self.rsd_flavors[flav_id]['rsd_systems'] chassis_ = self.rsd_flavors[flav_id]['rsd_systems']
if str(chas.path) not in chassis_.keys(): if str(chas.path) not in chassis_.keys():
@ -510,30 +503,34 @@ class RSDDriver(driver.ComputeDriver):
flav_id = str(mem) + 'MB-' + str(proc) + 'vcpus' flav_id = str(mem) + 'MB-' + str(proc) + 'vcpus'
flav_ids.append(flav_id) 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: for f in f_list:
if 'RSD' in f.name: if 'RSD' in f['name']:
if f.flavorid not in flav_ids: if f['id'] not in flav_ids:
try: del_url = self.flavor_manager._create_request_url(
flavor._flavor_destroy( f['id'], 'delete')
context.get_admin_context(), fla_del = requests.delete(del_url, headers=self.headers)
flavor_id=f.flavorid)
except exception.FlavorNotFound as ex:
LOG.warn("Flavor not found exception: %s", ex)
for k in list(self.rsd_flavors): for k in list(self.rsd_flavors):
if k in self.rsd_flavors.keys(): sys_list = self.rsd_flavors[k]['rsd_systems'].values()
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: for s in sys_list:
if s not in sys_ids: if s not in sys_ids:
try:
rsd_id = self.rsd_flavors[k]['id'] rsd_id = self.rsd_flavors[k]['id']
flavor._flavor_destroy( del_url = self.flavor_manager._create_request_url(
context.get_admin_context(), rsd_id) rsd_id, 'delete')
LOG.debug("Deleting flavor: %s", k) try:
LOG.debug("Deleting flavor for removed systems: %s", k)
fla_del = requests.delete(del_url,
headers=self.headers)
del self.rsd_flavors[k] del self.rsd_flavors[k]
except KeyError as k_ex: except Exception as ex:
LOG.warn("Flavor doesn't exist:%s", k_ex) LOG.warn("Failed to delete flavor: %s, %s",
json.loads(fla_del.text), ex)
return 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] [tox]
minversion = 2.0 minversion = 2.0
envlist = py27,py36,pycodestyle envlist = py27,py36,pep8
skipsdist = True skipsdist = True
[testenv] [testenv]
@ -23,7 +23,7 @@ deps = -r{toxinidir}/requirements.txt
-r{toxinidir}/test-requirements.txt -r{toxinidir}/test-requirements.txt
commands = stestr run {posargs} commands = stestr run {posargs}
[testenv:pycodestyle] [testenv:pep8]
commands = commands =
flake8 {posargs} flake8 {posargs}
pycodestyle {posargs} pycodestyle {posargs}