Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow target-level control parameter overrides #103

Merged
merged 3 commits into from
Jun 15, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
104 changes: 98 additions & 6 deletions gwcli/gateway.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
GatewayError, APIRequest, console_message, valid_iqn)

import ceph_iscsi_config.settings as settings
from ceph_iscsi_config.utils import format_lio_yes_no

import rtslib_fb.root as root

Expand Down Expand Up @@ -71,6 +72,11 @@ def refresh(self):
else:
self.target.gateway_group = {}

if 'controls' in self.config:
self.target.controls = self.config['controls']
else:
self.target.controls = {}

if 'clients' in self.config:
self.target.client_group = self.config['clients']
else:
Expand Down Expand Up @@ -355,31 +361,117 @@ def refresh(self):
self.reset()
if 'iqn' in self.gateway_group:
tgt = Target(self.gateway_group['iqn'], self)
tgt.controls = self.controls

tgt.gateway_group.load(self.gateway_group)
tgt.client_group.load(self.client_group)

def summary(self):
return "Targets: {}".format(len(self.children)), None


class Target(UIGroup):
class Target(UINode):

help_info = '''
The iscsi target is the name that the group of gateways are
known as by the iscsi initiators (clients).
'''
display_attributes = ["target_iqn", "control_values"]

help_intro = '''
The iscsi target is the name that the group of gateways are
known as by the iscsi initiators (clients).
'''
def __init__(self, target_iqn, parent):

UIGroup.__init__(self, target_iqn, parent)
self.target_iqn = target_iqn

self.control_values = []
self.controls = {}

self.gateway_group = GatewayGroup(self)
self.client_group = Clients(self)
self.host_groups = HostGroups(self)

def _get_controls(self):
return self._controls.copy()

def _set_controls(self, controls):
self._controls = controls.copy()
self._refresh_control_values()

controls = property(_get_controls, _set_controls)

def summary(self):
return "Gateways: {}".format(len(self.gateway_group.children)), None

def ui_command_reconfigure(self, attribute, value):
"""
The reconfigure command allows you to tune various gatway attributes.
An empty value for an attribute resets the lun attribute to its
default.
attribute : attribute to reconfigure. supported attributes:
- cmdsn_depth : integer
- dataout_timeout : integer
- nopin_response_timeout : integer
- nopin_timeout : integer
- immediate_data : [Yes|No]
- initial_r2t : [Yes|No]
- first_burst_length : integer
- max_burst_length : integer
- max_outstanding_r2t : integer
- max_recv_data_segment_length : integer
- max_xmit_data_segment_length : integer
value : value of the attribute to reconfigure
e.g.
set cmdsn_depth
- reconfigure attribute=cmdsn_depth value=128
reset cmdsn_depth
- reconfigure attribute=cmdsn_depth value=
"""
if not attribute in settings.Settings.GATEWAY_SETTINGS:
self.logger.error("supported attributes: {}".format(",".join(
sorted(settings.Settings.GATEWAY_SETTINGS))))
return

# Issue the api request for the reconfigure
gateways_api = ('{}://127.0.0.1:{}/api/'
'target/{}'.format(self.http_mode,
settings.config.api_port,
self.target_iqn))

controls = {attribute: value}
controls_json = json.dumps(controls)
api_vars = {'mode': 'reconfigure', 'controls': controls_json}

self.logger.debug("Issuing reconfigure request: controls={}".format(controls_json))
api = APIRequest(gateways_api, data=api_vars)
api.put()

if api.response.status_code != 200:
self.logger.error("Failed to reconfigure : "
"{}".format(response_message(api.response,
self.logger)))
return

config = self.parent.parent._get_config()
if not config:
self.logger.error("Unable to refresh local config")
self.controls = config.get('controls', {})

self.logger.info('ok')

def _refresh_control_values(self):
self.control_values = {}
for k in settings.Settings.GATEWAY_SETTINGS:
val = self._controls.get(k)
default_val = getattr(settings.config, k, None)
if k in settings.Settings.LIO_YES_NO_SETTINGS:
if val is not None:
val = format_lio_yes_no(val)
default_val = format_lio_yes_no(default_val)

if val is None or str(val) == str(default_val):
self.control_values[k] = default_val
else:
self.control_values[k] = "{} (override)".format(val)


class GatewayGroup(UIGroup):

Expand Down
176 changes: 152 additions & 24 deletions rbd-target-api.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import sys
import os
import signal
import json
import logging
import logging.handlers
import ssl
Expand Down Expand Up @@ -177,6 +178,25 @@ def get_sys_info(query_type=None):
# Request Unknown
return jsonify(message="Unknown /sysinfo query"), 404

def _parse_target_controls(controls_json):
controls = {}
raw_controls = json.loads(controls_json)
for k in settings.Settings.GATEWAY_SETTINGS:
if raw_controls.has_key(k):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

has_key has been deprecated in favor of 'in' (I think).
See http://portingguide.readthedocs.io/en/latest/dicts.html

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is already merged, so feel free to open a PR to incorporate your suggestions.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done - #110

value = raw_controls[k] if raw_controls[k] else None
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps:

value = raw_controls.get(k) ?

if value:
if k in settings.Settings.LIO_YES_NO_SETTINGS:
try:
value = settings.Settings.convert_lio_yes_no(value)
except ValueError:
raise ValueError("expected yes or no for {}".format(k))
else:
try:
value = str(int(value))
except ValueError:
raise ValueError("expected integer for {}".format(k))
controls[k] = value
return controls

@app.route('/api/target/<target_iqn>', methods=['PUT'])
@requires_restricted_auth
Expand All @@ -186,38 +206,147 @@ def target(target_iqn=None):
The target is added to the configuration object, seeding the configuration
for ALL gateways
:param target_iqn: IQN of the target each gateway will use
:param mode: (str) 'reconfigure'
:param controls: (JSON dict) valid control overrides
**RESTRICTED**
Examples:
curl --insecure --user admin:admin -X PUT http://192.168.122.69:5000/api/target/iqn.2003-01.com.redhat.iscsi-gw0
curl --insecure --user admin:admin -d mode=reconfigure -d controls='{cmdsn_depth=128}' -X PUT http://192.168.122.69:5000/api/target/iqn.2003-01.com.redhat.iscsi-gw0
"""
if request.method == 'PUT':

gateway_ip_list = []
mode = request.form.get('mode', None)
if mode not in [None, 'reconfigure']:
logger.error("Unexpected mode provided")
return jsonify(message="Unexpected mode provided for {} - "
"{}".format(target_iqn, mode)), 500

target = GWTarget(logger,
str(target_iqn),
gateway_ip_list)
controls = {}
if request.form.has_key('controls'):
try:
controls = _parse_target_controls(request.form['controls'])
except ValueError as err:
logger.error("Unexpected or invalid controls")
return jsonify(message="Unexpected or invalid controls - "
"{}".format(err)), 500
logger.debug("controls {}".format(controls))

gateway_ip_list = []
target = GWTarget(logger,
str(target_iqn),
gateway_ip_list)

if target.error:
logger.error("Unable to create an instance of the GWTarget class")
return jsonify(message="GWTarget problem - "
"{}".format(target.error_msg)), 500

for k, v in controls.iteritems():
setattr(target, k, v)

target.manage('init')
if target.error:
logger.error("Failure during gateway 'init' processing")
return jsonify(message="iscsi target 'init' process failed "
"for {} - {}".format(target_iqn,
target.error_msg)), 500

if mode is None:
config.refresh()
return jsonify(message="Target defined successfully"), 200

if target.error:
logger.error("Unable to create an instance of the GWTarget class")
return jsonify(message="GWTarget problem - "
"{}".format(target.error_msg)), 500
target.manage('reconfigure')
if target.error:
logger.error("Failure during gateway 'reconfigure' processing")
return jsonify(message="iscsi target 'reconfigure' process failed "
"for {} - {}".format(target_iqn,
target.error_msg)), 500
config.refresh()

target.manage('init')
if target.error:
logger.error("Failure during gateway 'init' processing")
return jsonify(message="iscsi target 'init' process failed "
"for {} - {}".format(target_iqn,
target.error_msg)), 500
# This is a reconfigure operation, so first confirm the gateways
# are in place (we need defined gateways)
local_gw = this_host()
logger.debug("this host is {}".format(local_gw))
gateways = [key for key in config.config['gateways']
if isinstance(config.config['gateways'][key], dict)]
logger.debug("other gateways - {}".format(gateways))
gateways.remove(local_gw)
gateways.append(local_gw)

config.refresh()
return jsonify(message="Target defined successfully"), 200
resp_text, resp_code = call_api(gateways, '_target', target_iqn,
http_method='put',
api_vars=request.form)
return jsonify(message="Target reconfigure {}".format(resp_text)), resp_code

else:
# return unrecognised request
return jsonify(message="Invalid method ({}) to target "
"API".format(request.method)), 405
@app.route('/api/_target/<target_iqn>', methods=['PUT'])
@requires_restricted_auth
def _target(target_iqn=None):
mode = request.form.get('mode', None)
if mode not in ['reconfigure']:
logger.error("Unexpected mode provided")
return jsonify(message="Unexpected mode provided for {} - "
"{}".format(target_iqn, mode)), 500

controls = {}
if request.form.has_key('controls'):
try:
controls = _parse_target_controls(request.form['controls'])
except ValueError as err:
logger.error("Unexpected or invalid controls")
return jsonify(message="Unexpected or invalid controls - "
"{}".format(err)), 500
logger.debug("controls {}".format(controls))

config.refresh()

target = GWTarget(logger, str(target_iqn), [])
if target.error:
logger.error("Unable to create an instance of the GWTarget class")
return jsonify(message="GWTarget problem - "
"{}".format(target.error_msg)), 500

for k,v in controls.iteritems():
setattr(target, k, v)

if target.exists():
target.load_config()
if target.error:
logger.error("Unable to refresh tpg state")
return jsonify(message="Unable to refresh tpg state - "
"{}".format(target.error_msg)), 500

target.update_tpg_controls()
if target.error:
logger.error("Unable to update tpg control overrides")
return jsonify(message="Unable to update tpg control overrides - "
"{}".format(target.error_msg)), 500

# re-apply client control overrides
client_errors = False
for client_iqn in config.config['clients']:
client_metadata = config.config['clients'][client_iqn]
image_list = client_metadata['luns'].keys()
client_chap = CHAP(client_metadata['auth']['chap'])
chap_str = client_chap.chap_str
if client_chap.error:
logger.debug("Password decode issue : "
"{}".format(client_chap.error_msg))
halt("Unable to decode password for "
"{}".format(client_iqn))

client = GWClient(logger,
client_iqn,
image_list,
chap_str)
client.manage('reconfigure')
if client.error:
logger.error("Client control override failed "
"{} - {}".format(client_iqn,
client.error_msg))
client_errors = True
if client_errors:
return jsonify(message="Client control override failed"), 500

return jsonify(message="Target reconfigured successfully"), 200

@app.route('/api/config', methods=['GET'])
@requires_restricted_auth
Expand Down Expand Up @@ -247,7 +376,6 @@ def gateways():
if request.method == 'GET':
return jsonify(config.config['gateways']), 200


@app.route('/api/gateway/<gateway_name>', methods=['PUT'])
@requires_restricted_auth
def gateway(gateway_name=None):
Expand Down Expand Up @@ -751,7 +879,7 @@ def _disk(image_id):
if client.error:
logger.error("LUN mapping failed "
"{} - {}".format(client_iqn,
client.error_str))
client.error_msg))
client_errors = True

# re-map LUN to host groups
Expand All @@ -767,7 +895,7 @@ def _disk(image_id):
if group.error:
logger.error("LUN mapping failed "
"{} - {}".format(group_iqn,
group.error_str))
group.error_msg))
client_errors = True

if client_errors:
Expand Down