Hello community, here is the log from the commit of package ceph-iscsi for openSUSE:Factory checked in at 2019-04-09 20:18:42 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/ceph-iscsi (Old) and /work/SRC/openSUSE:Factory/.ceph-iscsi.new.3908 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "ceph-iscsi" Tue Apr 9 20:18:42 2019 rev:9 rq:692489 version:3.0+1554735444.g63aceaf Changes: -------- --- /work/SRC/openSUSE:Factory/ceph-iscsi/ceph-iscsi.changes 2019-03-26 15:45:13.968092820 +0100 +++ /work/SRC/openSUSE:Factory/.ceph-iscsi.new.3908/ceph-iscsi.changes 2019-04-09 20:18:42.421844161 +0200 @@ -1,0 +2,10 @@ +Mon Apr 8 14:35:04 UTC 2019 - [email protected] + +- Update to 3.0+1554735444.g63aceaf: + + Adds endpoints: + * /api/targetinfo/<target_iqn> + * /api/gatewayinfo + * /api/clientinfo/<target_iqn>/<client_iqn> + + Fix upgrade from config v3 when 'controls' field is missing + +------------------------------------------------------------------- Old: ---- ceph-iscsi-3.0+1553528639.g1149ac6.tar.gz New: ---- ceph-iscsi-3.0+1554735444.g63aceaf.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ ceph-iscsi.spec ++++++ --- /var/tmp/diff_new_pack.Ygo303/_old 2019-04-09 20:18:43.901846179 +0200 +++ /var/tmp/diff_new_pack.Ygo303/_new 2019-04-09 20:18:43.901846179 +0200 @@ -20,7 +20,7 @@ Name: ceph-iscsi -Version: 3.0+1553528639.g1149ac6 +Version: 3.0+1554735444.g63aceaf Release: 1%{?dist} Group: System/Filesystems Summary: Python modules for Ceph iSCSI gateway configuration management ++++++ ceph-iscsi-3.0+1553528639.g1149ac6.tar.gz -> ceph-iscsi-3.0+1554735444.g63aceaf.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ceph-iscsi-3.0+1553528639.g1149ac6/ceph-iscsi.spec new/ceph-iscsi-3.0+1554735444.g63aceaf/ceph-iscsi.spec --- old/ceph-iscsi-3.0+1553528639.g1149ac6/ceph-iscsi.spec 2019-03-25 16:43:59.851691154 +0100 +++ new/ceph-iscsi-3.0+1554735444.g63aceaf/ceph-iscsi.spec 2019-04-08 16:57:24.610585206 +0200 @@ -20,7 +20,7 @@ Name: ceph-iscsi -Version: 3.0+1553528639.g1149ac6 +Version: 3.0+1554735444.g63aceaf Release: 1%{?dist} Group: System/Filesystems Summary: Python modules for Ceph iSCSI gateway configuration management diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ceph-iscsi-3.0+1553528639.g1149ac6/ceph_iscsi_config/client.py new/ceph-iscsi-3.0+1554735444.g63aceaf/ceph_iscsi_config/client.py --- old/ceph-iscsi-3.0+1553528639.g1149ac6/ceph_iscsi_config/client.py 2019-03-25 16:43:59.583689649 +0100 +++ new/ceph-iscsi-3.0+1554735444.g63aceaf/ceph_iscsi_config/client.py 2019-04-08 16:57:24.310583510 +0200 @@ -235,6 +235,34 @@ self.change_count += 1 @staticmethod + def get_client_info(target_iqn, client_iqn): + result = { + "alias": '', + "state": '', + "ip_address": [] + } + iscsi_fabric = ISCSIFabricModule() + target = Target(iscsi_fabric, target_iqn, 'lookup') + for tpg in target.tpgs: + if tpg.enable: + for client in tpg.node_acls: + if client.node_wwn != client_iqn: + continue + session = client.session + if session is None: + break + result['alias'] = session.get('alias') + state = session.get('state').upper() + result['state'] = state + ips = set() + if state == 'LOGGED_IN': + for conn in session.get('connections'): + ips.add(conn.get('address')) + result['ip_address'] = list(ips) + break + return result + + @staticmethod def define_clients(logger, config, target_iqn): """ define the clients (nodeACLs) to the gateway definition diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ceph-iscsi-3.0+1553528639.g1149ac6/ceph_iscsi_config/common.py new/ceph-iscsi-3.0+1554735444.g63aceaf/ceph_iscsi_config/common.py --- old/ceph-iscsi-3.0+1553528639.g1149ac6/ceph_iscsi_config/common.py 2019-03-25 16:43:59.583689649 +0100 +++ new/ceph-iscsi-3.0+1554735444.g63aceaf/ceph_iscsi_config/common.py 2019-04-08 16:57:24.310583510 +0200 @@ -199,7 +199,7 @@ 'clients': self.config['clients'], 'portals': portals, 'groups': self.config['groups'], - 'controls': self.config['controls'], + 'controls': self.config.get('controls', {}), 'ip_list': self.config['gateways']['ip_list'] } self.add_item('discovery_auth', None, { @@ -209,7 +209,8 @@ self.add_item("targets", None, {}) self.add_item("targets", iqn, target) self.update_item("targets", iqn, target) - self.del_item('controls', None) + if 'controls' in self.config: + self.del_item('controls', None) self.del_item('clients', None) self.del_item('groups', None) self.update_item("gateways", None, gateways) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ceph-iscsi-3.0+1553528639.g1149ac6/ceph_iscsi_config/target.py new/ceph-iscsi-3.0+1554735444.g63aceaf/ceph_iscsi_config/target.py --- old/ceph-iscsi-3.0+1553528639.g1149ac6/ceph_iscsi_config/target.py 2019-03-25 16:43:59.583689649 +0100 +++ new/ceph-iscsi-3.0+1554735444.g63aceaf/ceph_iscsi_config/target.py 2019-04-08 16:57:24.310583510 +0200 @@ -709,3 +709,9 @@ config.del_item('gateways', local_gw) config.commit() + + @staticmethod + def get_num_sessions(target_iqn): + with open('/sys/kernel/config/target/iscsi/{}/fabric_statistics/iscsi_instance' + '/sessions'.format(target_iqn)) as sessions_file: + return int(sessions_file.read().rstrip('\n')) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ceph-iscsi-3.0+1553528639.g1149ac6/gwcli/client.py new/ceph-iscsi-3.0+1554735444.g63aceaf/gwcli/client.py --- old/ceph-iscsi-3.0+1553528639.g1149ac6/gwcli/client.py 2019-03-25 16:43:59.583689649 +0100 +++ new/ceph-iscsi-3.0+1554735444.g63aceaf/gwcli/client.py 2019-04-08 16:57:24.314583532 +0200 @@ -2,11 +2,10 @@ from gwcli.utils import response_message, APIRequest, get_config -from ceph_iscsi_config.client import CHAP +from ceph_iscsi_config.client import CHAP, GWClient import ceph_iscsi_config.settings as settings from ceph_iscsi_config.utils import human_size -import rtslib_fb.root as root from rtslib_fb.utils import normalize_wwn, RTSLibError # this ignores the warning issued when verify=False is used @@ -690,22 +689,11 @@ @property def logged_in(self): - - r = root.RTSRoot() - for sess in r.sessions: - if sess['parent_nodeacl'].node_wwn == self.client_iqn: - self.alias = sess.get('alias') - state = sess.get('state').upper() - ips = set() - if state == 'LOGGED_IN': - for conn in sess.get('connections'): - ips.add(conn.get('address')) - self.ip_address = ','.join(list(ips)) - else: - self.ip_address = '' - - return state - return '' + target_iqn = self.parent.parent.name + client_info = GWClient.get_client_info(target_iqn, self.client_iqn) + self.alias = client_info['alias'] + self.ip_address = ','.join(client_info['ip_address']) + return client_info['state'] class MappedLun(UINode): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ceph-iscsi-3.0+1553528639.g1149ac6/gwcli/utils.py new/ceph-iscsi-3.0+1554735444.g63aceaf/gwcli/utils.py --- old/ceph-iscsi-3.0+1553528639.g1149ac6/gwcli/utils.py 2019-03-25 16:43:59.587689671 +0100 +++ new/ceph-iscsi-3.0+1554735444.g63aceaf/gwcli/utils.py 2019-04-08 16:57:24.314583532 +0200 @@ -8,8 +8,8 @@ from rtslib_fb.utils import normalize_wwn, RTSLibError -import rtslib_fb.root as root +from ceph_iscsi_config.client import GWClient import ceph_iscsi_config.settings as settings from ceph_iscsi_config.utils import (resolve_ip_addresses, gen_file_hash, CephiSCSIError) @@ -283,14 +283,10 @@ # client to delete must not be logged in - we're just checking locally, # since *all* nodes are set up the same, and a client login request # would normally login to each gateway - lio_root = root.RTSRoot() - clients_logged_in = [session['parent_nodeacl'].node_wwn - for session in lio_root.sessions - if session['state'] == 'LOGGED_IN'] - - if client_iqn in clients_logged_in: - return ("Client '{}' is logged in - unable to delete until" - " it's logged out".format(client_iqn)) + client_info = GWClient.get_client_info(target_iqn, client_iqn) + if client_info['state'] == 'LOGGED_IN': + return ("Client '{}' is logged in to {}- unable to delete until" + " it's logged out".format(client_iqn, target_iqn)) # at this point, the client looks ok for a DELETE operation return 'ok' diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ceph-iscsi-3.0+1553528639.g1149ac6/rbd-target-api.py new/ceph-iscsi-3.0+1554735444.g63aceaf/rbd-target-api.py --- old/ceph-iscsi-3.0+1553528639.g1149ac6/rbd-target-api.py 2019-03-25 16:43:59.587689671 +0100 +++ new/ceph-iscsi-3.0+1554735444.g63aceaf/rbd-target-api.py 2019-04-08 16:57:24.314583532 +0200 @@ -1618,6 +1618,76 @@ return jsonify(message='OK'), 200 [email protected]('/api/targetinfo/<target_iqn>', methods=['GET']) +@requires_restricted_auth +def targetinfo(target_iqn): + """ + Returns the total number of active sessions for <target_iqn> + **RESTRICTED** + Examples: + curl --insecure --user admin:admin -X GET + http://192.168.122.69:5000/api/targetinfo/iqn.2003-01.com.redhat.iscsi-gw:iscsi-igw + """ + if target_iqn not in config.config['targets']: + return jsonify(message="Target {} does not exist".format(target_iqn)), 400 + target_config = config.config['targets'][target_iqn] + gateways = target_config['portals'] + num_sessions = 0 + for gateway in gateways.keys(): + resp_text, resp_code = call_api([gateway], '_targetinfo', target_iqn, http_method='get') + if resp_code != 200: + return jsonify(message="{}".format(resp_text)), resp_code + gateway_response = json.loads(resp_text) + num_sessions += gateway_response['num_sessions'] + return jsonify({ + "num_sessions": num_sessions + }), 200 + + [email protected]('/api/_targetinfo/<target_iqn>', methods=['GET']) +@requires_restricted_auth +def _targetinfo(target_iqn): + """ + Returns the number of active sessions for <target_iqn> on local gateway + **RESTRICTED** + Examples: + curl --insecure --user admin:admin -X GET + http://192.168.122.69:5000/api/_targetinfo/iqn.2003-01.com.redhat.iscsi-gw:iscsi-igw + """ + if target_iqn not in config.config['targets']: + return jsonify(message="Target {} does not exist".format(target_iqn)), 400 + target_config = config.config['targets'][target_iqn] + local_gw = this_host() + if local_gw not in target_config['portals']: + return jsonify(message="{} is not a portal of target {}".format(local_gw, target_iqn)), 400 + num_sessions = GWTarget.get_num_sessions(target_iqn) + return jsonify({ + "num_sessions": num_sessions + }), 200 + + [email protected]('/api/gatewayinfo', methods=['GET']) +@requires_restricted_auth +def gatewayinfo(): + """ + Returns the number of active sessions on local gateway + **RESTRICTED** + Examples: + curl --insecure --user admin:admin -X GET + http://192.168.122.69:5000/api/gatewayinfo + """ + local_gw = this_host() + if local_gw not in config.config['gateways']: + return jsonify(message="Gateway {} does not exist in configuration".format(local_gw)), 400 + num_sessions = 0 + for target_iqn, target in config.config['targets'].items(): + if local_gw in target['portals']: + num_sessions += GWTarget.get_num_sessions(target_iqn) + return jsonify({ + "num_sessions": num_sessions + }), 200 + + @app.route('/api/clients/<target_iqn>', methods=['GET']) @requires_restricted_auth def get_clients(target_iqn=None): @@ -2059,6 +2129,73 @@ return jsonify(message="Client does not exist!"), 404 [email protected]('/api/clientinfo/<target_iqn>/<client_iqn>', methods=['GET']) +@requires_restricted_auth +def clientinfo(target_iqn, client_iqn): + """ + Returns client alias, ip_address and state for each connected portal + **RESTRICTED** + Examples: + curl --insecure --user admin:admin -X GET + http://192.168.122.69:5000/api/clientinfo/ + iqn.2003-01.com.redhat.iscsi-gw:iscsi-igw/iqn.2003-01.com.redhat.iscsi-gw:iscsi-igw-client + """ + if target_iqn not in config.config['targets']: + return jsonify(message="Target {} does not exist".format(target_iqn)), 400 + target_config = config.config['targets'][target_iqn] + if client_iqn not in target_config['clients']: + return jsonify(message="Client {} does not exist".format(client_iqn)), 400 + gateways = target_config['portals'] + response = { + "alias": '', + "state": {}, + "ip_address": [] + } + for gateway in gateways.keys(): + resp_text, resp_code = call_api([gateway], + '_clientinfo', + '{}/{}'.format(target_iqn, client_iqn), + http_method='get') + if resp_code != 200: + return jsonify(message="{}".format(resp_text)), resp_code + gateway_response = json.loads(resp_text) + alias = gateway_response['alias'] + if alias: + response['alias'] = gateway_response['alias'] + state = gateway_response['state'] + if state: + if state not in response['state']: + response['state'][state] = [] + response['state'][state].append(gateway) + response['ip_address'].extend(gateway_response['ip_address']) + response['ip_address'] = list(set(response['ip_address'])) + return jsonify(response), 200 + + [email protected]('/api/_clientinfo/<target_iqn>/<client_iqn>', methods=['GET']) +@requires_restricted_auth +def _clientinfo(target_iqn, client_iqn): + """ + Returns client alias, ip_address and state for local gateway + **RESTRICTED** + Examples: + curl --insecure --user admin:admin -X GET + http://192.168.122.69:5000/api/_clientinfo/ + iqn.2003-01.com.redhat.iscsi-gw:iscsi-igw/iqn.2003-01.com.redhat.iscsi-gw:iscsi-igw-client + """ + if target_iqn not in config.config['targets']: + return jsonify(message="Target {} does not exist".format(target_iqn)), 400 + target_config = config.config['targets'][target_iqn] + if client_iqn not in target_config['clients']: + return jsonify(message="Client {} does not exist".format(client_iqn)), 400 + local_gw = this_host() + if local_gw not in target_config['portals']: + return jsonify(message="{} is not a portal of target {}".format(local_gw, target_iqn)), 400 + + logged_in = GWClient.get_client_info(target_iqn, client_iqn) + return jsonify(logged_in), 200 + + @app.route('/api/hostgroups/<target_iqn>', methods=['GET']) @requires_restricted_auth def hostgroups(target_iqn=None): @@ -2444,7 +2581,7 @@ return fail_msg, api.response.status_code - return "successful", 200 + return api.response.text if http_method == 'get' else 'successful', 200 def pre_reqs_errors():
