1
0
mirror of https://github.com/ceph/ceph synced 2024-12-22 03:22:00 +00:00

Merge pull request from ricardoasmarques/iscsi-management-w

mgr/dashboard: iSCSI management API

Reviewed-by: Tiago Melo <tmelo@suse.com>
This commit is contained in:
Lenz Grimmer 2019-01-30 15:56:48 +01:00 committed by GitHub
commit d239c2a8b4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 1438 additions and 0 deletions
doc/mgr
src/pybind/mgr/dashboard

View File

@ -258,6 +258,19 @@ into timeouts, then you can set the timeout value to your needs::
The default value is 45 seconds.
Enabling iSCSI Management
^^^^^^^^^^^^^^^^^^^^^^^^^
The Ceph Manager Dashboard can manage iSCSI targets using the REST API provided
by the `rbd-target-api` service of the `ceph-iscsi <https://github.com/ceph/ceph-iscsi>`_
project. Please make sure that it's installed and enabled on the iSCSI gateways.
The available iSCSI gateways must be defined using the following commands::
$ ceph dashboard iscsi-gateway-list
$ ceph dashboard iscsi-gateway-add <gateway_name> <scheme>://<username>:<password>@<host>[:port]
$ ceph dashboard iscsi-gateway-rm <gateway_name>
Enabling the Embedding of Grafana Dashboards
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

View File

@ -0,0 +1,538 @@
# -*- coding: utf-8 -*-
# pylint: disable=too-many-branches
from __future__ import absolute_import
from copy import deepcopy
import json
import cherrypy
import rados
import rbd
from . import ApiController, UiApiController, RESTController, BaseController, Endpoint,\
ReadPermission, Task
from .. import mgr
from ..rest_client import RequestException
from ..security import Scope
from ..services.iscsi_client import IscsiClient
from ..services.iscsi_cli import IscsiGatewaysConfig
from ..exceptions import DashboardException
from ..tools import TaskManager
@UiApiController('/iscsi', Scope.ISCSI)
class Iscsi(BaseController):
@Endpoint()
@ReadPermission
def status(self):
status = {'available': False}
if not IscsiGatewaysConfig.get_gateways_config()['gateways']:
status['message'] = 'There are no gateways defined'
return status
try:
IscsiClient.instance().get_config()
status['available'] = True
except RequestException as e:
if e.content:
content = json.loads(e.content)
content_message = content.get('message')
if content_message:
status['message'] = content_message
return status
@Endpoint()
@ReadPermission
def settings(self):
return IscsiClient.instance().get_settings()
@Endpoint()
@ReadPermission
def portals(self):
portals = []
gateways_config = IscsiGatewaysConfig.get_gateways_config()
for name in gateways_config['gateways'].keys():
ip_addresses = IscsiClient.instance(gateway_name=name).get_ip_addresses()
portals.append({'name': name, 'ip_addresses': ip_addresses['data']})
return sorted(portals, key=lambda p: '{}.{}'.format(p['name'], p['ip_addresses']))
def iscsi_target_task(name, metadata, wait_for=2.0):
return Task("iscsi/target/{}".format(name), metadata, wait_for)
@ApiController('/iscsi/target', Scope.ISCSI)
class IscsiTarget(RESTController):
def list(self):
config = IscsiClient.instance().get_config()
targets = []
for target_iqn in config['targets'].keys():
target = IscsiTarget._config_to_target(target_iqn, config)
targets.append(target)
return targets
def get(self, target_iqn):
config = IscsiClient.instance().get_config()
if target_iqn not in config['targets']:
raise cherrypy.HTTPError(404)
return IscsiTarget._config_to_target(target_iqn, config)
@iscsi_target_task('delete', {'target_iqn': '{target_iqn}'})
def delete(self, target_iqn):
config = IscsiClient.instance().get_config()
if target_iqn not in config['targets']:
raise DashboardException(msg='Target does not exist',
code='target_does_not_exist',
component='iscsi')
if target_iqn not in config['targets']:
raise DashboardException(msg='Target does not exist',
code='target_does_not_exist',
component='iscsi')
IscsiTarget._delete(target_iqn, config, 0, 100)
@iscsi_target_task('create', {'target_iqn': '{target_iqn}'})
def create(self, target_iqn=None, target_controls=None,
portals=None, disks=None, clients=None, groups=None):
target_controls = target_controls or {}
portals = portals or []
disks = disks or []
clients = clients or []
groups = groups or []
config = IscsiClient.instance().get_config()
if target_iqn in config['targets']:
raise DashboardException(msg='Target already exists',
code='target_already_exists',
component='iscsi')
IscsiTarget._validate(target_iqn, portals, disks)
IscsiTarget._create(target_iqn, target_controls, portals, disks, clients, groups, 0, 100,
config)
@iscsi_target_task('edit', {'target_iqn': '{target_iqn}'})
def set(self, target_iqn, new_target_iqn=None, target_controls=None,
portals=None, disks=None, clients=None, groups=None):
target_controls = target_controls or {}
portals = IscsiTarget._sorted_portals(portals)
disks = IscsiTarget._sorted_disks(disks)
clients = IscsiTarget._sorted_clients(clients)
groups = IscsiTarget._sorted_groups(groups)
config = IscsiClient.instance().get_config()
if target_iqn not in config['targets']:
raise DashboardException(msg='Target does not exist',
code='target_does_not_exist',
component='iscsi')
if target_iqn != new_target_iqn and new_target_iqn in config['targets']:
raise DashboardException(msg='Target IQN already in use',
code='target_iqn_already_in_use',
component='iscsi')
IscsiTarget._validate(new_target_iqn, portals, disks)
config = IscsiTarget._delete(target_iqn, config, 0, 50, new_target_iqn, target_controls,
portals, disks, clients, groups)
IscsiTarget._create(new_target_iqn, target_controls, portals, disks, clients, groups,
50, 100, config)
@staticmethod
def _delete(target_iqn, config, task_progress_begin, task_progress_end, new_target_iqn=None,
new_target_controls=None, new_portals=None, new_disks=None, new_clients=None,
new_groups=None):
new_target_controls = new_target_controls or {}
new_portals = new_portals or []
new_disks = new_disks or []
new_clients = new_clients or []
new_groups = new_groups or []
TaskManager.current_task().set_progress(task_progress_begin)
target_config = config['targets'][target_iqn]
if not target_config['portals'].keys():
raise DashboardException(msg="Cannot delete a target that doesn't contain any portal",
code='cannot_delete_target_without_portals',
component='iscsi')
target = IscsiTarget._config_to_target(target_iqn, config)
n_groups = len(target_config['groups'])
n_clients = len(target_config['clients'])
n_target_disks = len(target_config['disks'])
task_progress_steps = n_groups + n_clients + n_target_disks
task_progress_inc = 0
if task_progress_steps != 0:
task_progress_inc = int((task_progress_end - task_progress_begin) / task_progress_steps)
gateway_name = list(target_config['portals'].keys())[0]
deleted_groups = []
for group_id in list(target_config['groups'].keys()):
if IscsiTarget._group_deletion_required(target, new_target_iqn, new_target_controls,
new_portals, new_groups, group_id, new_clients,
new_disks):
deleted_groups.append(group_id)
IscsiClient.instance(gateway_name=gateway_name).delete_group(target_iqn,
group_id)
TaskManager.current_task().inc_progress(task_progress_inc)
for client_iqn in list(target_config['clients'].keys()):
if IscsiTarget._client_deletion_required(target, new_target_iqn, new_target_controls,
new_portals, new_clients, client_iqn,
new_groups, deleted_groups):
IscsiClient.instance(gateway_name=gateway_name).delete_client(target_iqn,
client_iqn)
TaskManager.current_task().inc_progress(task_progress_inc)
for image_id in target_config['disks']:
if IscsiTarget._target_lun_deletion_required(target, new_target_iqn,
new_target_controls, new_portals,
new_disks, image_id):
IscsiClient.instance(gateway_name=gateway_name).delete_target_lun(target_iqn,
image_id)
IscsiClient.instance(gateway_name=gateway_name).delete_disk(image_id)
TaskManager.current_task().inc_progress(task_progress_inc)
if IscsiTarget._target_deletion_required(target, new_target_iqn, new_target_controls,
new_portals):
IscsiClient.instance(gateway_name=gateway_name).delete_target(target_iqn)
TaskManager.current_task().set_progress(task_progress_end)
return IscsiClient.instance(gateway_name=gateway_name).get_config()
@staticmethod
def _get_group(groups, group_id):
for group in groups:
if group['group_id'] == group_id:
return group
return None
@staticmethod
def _group_deletion_required(target, new_target_iqn, new_target_controls, new_portals,
new_groups, group_id, new_clients, new_disks):
if IscsiTarget._target_deletion_required(target, new_target_iqn, new_target_controls,
new_portals):
return True
new_group = IscsiTarget._get_group(new_groups, group_id)
if not new_group:
return True
old_group = IscsiTarget._get_group(target['groups'], group_id)
if new_group != old_group:
return True
# Check if any client inside this group has changed
for client_iqn in new_group['members']:
if IscsiTarget._client_deletion_required(target, new_target_iqn, new_target_controls,
new_portals, new_clients, client_iqn,
new_groups, []):
return True
# Check if any disk inside this group has changed
for disk in new_group['disks']:
image_id = '{}.{}'.format(disk['pool'], disk['image'])
if IscsiTarget._target_lun_deletion_required(target, new_target_iqn,
new_target_controls, new_portals,
new_disks, image_id):
return True
return False
@staticmethod
def _get_client(clients, client_iqn):
for client in clients:
if client['client_iqn'] == client_iqn:
return client
return None
@staticmethod
def _client_deletion_required(target, new_target_iqn, new_target_controls, new_portals,
new_clients, client_iqn, new_groups, deleted_groups):
if IscsiTarget._target_deletion_required(target, new_target_iqn, new_target_controls,
new_portals):
return True
new_client = deepcopy(IscsiTarget._get_client(new_clients, client_iqn))
if not new_client:
return True
# Disks inherited from groups must be considered
for group in new_groups:
if client_iqn in group['members']:
new_client['luns'] += group['disks']
old_client = IscsiTarget._get_client(target['clients'], client_iqn)
if new_client != old_client:
return True
# Check if client belongs to a groups that has been deleted
for group in target['groups']:
if group['group_id'] in deleted_groups and client_iqn in group['members']:
return True
return False
@staticmethod
def _get_disk(disks, image_id):
for disk in disks:
if '{}.{}'.format(disk['pool'], disk['image']) == image_id:
return disk
return None
@staticmethod
def _target_lun_deletion_required(target, new_target_iqn, new_target_controls, new_portals,
new_disks, image_id):
if IscsiTarget._target_deletion_required(target, new_target_iqn, new_target_controls,
new_portals):
return True
new_disk = IscsiTarget._get_disk(new_disks, image_id)
if not new_disk:
return True
old_disk = IscsiTarget._get_disk(target['disks'], image_id)
if new_disk != old_disk:
return True
return False
@staticmethod
def _target_deletion_required(target, new_target_iqn, new_target_controls, new_portals):
if target['target_iqn'] != new_target_iqn:
return True
if target['target_controls'] != new_target_controls:
return True
if target['portals'] != new_portals:
return True
return False
@staticmethod
def _validate(target_iqn, portals, disks):
if not target_iqn:
raise DashboardException(msg='Target IQN is required',
code='target_iqn_required',
component='iscsi')
settings = IscsiClient.instance().get_settings()
minimum_gateways = max(1, settings['config']['minimum_gateways'])
portals_by_host = IscsiTarget._get_portals_by_host(portals)
if len(portals_by_host.keys()) < minimum_gateways:
if minimum_gateways == 1:
msg = 'At least one portal is required'
else:
msg = 'At least {} portals are required'.format(minimum_gateways)
raise DashboardException(msg=msg,
code='portals_required',
component='iscsi')
for portal in portals:
gateway_name = portal['host']
try:
IscsiClient.instance(gateway_name=gateway_name).ping()
except RequestException:
raise DashboardException(msg='iSCSI REST Api not available for gateway '
'{}'.format(gateway_name),
code='ceph_iscsi_rest_api_not_available_for_gateway',
component='iscsi')
for disk in disks:
pool = disk['pool']
image = disk['image']
IscsiTarget._validate_image_exists(pool, image)
@staticmethod
def _validate_image_exists(pool, image):
try:
ioctx = mgr.rados.open_ioctx(pool)
try:
rbd.Image(ioctx, image)
except rbd.ImageNotFound:
raise DashboardException(msg='Image {} does not exist'.format(image),
code='image_does_not_exist',
component='iscsi')
except rados.ObjectNotFound:
raise DashboardException(msg='Pool {} does not exist'.format(pool),
code='pool_does_not_exist',
component='iscsi')
@staticmethod
def _create(target_iqn, target_controls,
portals, disks, clients, groups,
task_progress_begin, task_progress_end, config):
target_config = config['targets'].get(target_iqn, None)
TaskManager.current_task().set_progress(task_progress_begin)
portals_by_host = IscsiTarget._get_portals_by_host(portals)
n_hosts = len(portals_by_host)
n_disks = len(disks)
n_clients = len(clients)
n_groups = len(groups)
task_progress_steps = n_hosts + n_disks + n_clients + n_groups
task_progress_inc = 0
if task_progress_steps != 0:
task_progress_inc = int((task_progress_end - task_progress_begin) / task_progress_steps)
try:
gateway_name = portals[0]['host']
if not target_config:
IscsiClient.instance(gateway_name=gateway_name).create_target(target_iqn,
target_controls)
for host, ip_list in portals_by_host.items():
IscsiClient.instance(gateway_name=gateway_name).create_gateway(target_iqn,
host,
ip_list)
TaskManager.current_task().inc_progress(task_progress_inc)
for disk in disks:
pool = disk['pool']
image = disk['image']
image_id = '{}.{}'.format(pool, image)
if image_id not in config['disks']:
IscsiClient.instance(gateway_name=gateway_name).create_disk(image_id)
if not target_config or image_id not in target_config['disks']:
IscsiClient.instance(gateway_name=gateway_name).create_target_lun(target_iqn,
image_id)
controls = disk['controls']
if controls:
IscsiClient.instance(gateway_name=gateway_name).reconfigure_disk(image_id,
controls)
TaskManager.current_task().inc_progress(task_progress_inc)
for client in clients:
client_iqn = client['client_iqn']
if not target_config or client_iqn not in target_config['clients']:
IscsiClient.instance(gateway_name=gateway_name).create_client(target_iqn,
client_iqn)
for lun in client['luns']:
pool = lun['pool']
image = lun['image']
image_id = '{}.{}'.format(pool, image)
IscsiClient.instance(gateway_name=gateway_name).create_client_lun(
target_iqn, client_iqn, image_id)
user = client['auth']['user']
password = client['auth']['password']
chap = '{}/{}'.format(user, password) if user and password else ''
m_user = client['auth']['mutual_user']
m_password = client['auth']['mutual_password']
m_chap = '{}/{}'.format(m_user, m_password) if m_user and m_password else ''
IscsiClient.instance(gateway_name=gateway_name).create_client_auth(
target_iqn, client_iqn, chap, m_chap)
TaskManager.current_task().inc_progress(task_progress_inc)
for group in groups:
group_id = group['group_id']
members = group['members']
image_ids = []
for disk in group['disks']:
image_ids.append('{}.{}'.format(disk['pool'], disk['image']))
if not target_config or group_id not in target_config['groups']:
IscsiClient.instance(gateway_name=gateway_name).create_group(
target_iqn, group_id, members, image_ids)
TaskManager.current_task().inc_progress(task_progress_inc)
if target_controls:
if not target_config or target_controls != target_config['controls']:
IscsiClient.instance(gateway_name=gateway_name).reconfigure_target(
target_iqn, target_controls)
TaskManager.current_task().set_progress(task_progress_end)
except RequestException as e:
if e.content:
content = json.loads(e.content)
content_message = content.get('message')
if content_message:
raise DashboardException(msg=content_message, component='iscsi')
raise DashboardException(e=e, component='iscsi')
@staticmethod
def _config_to_target(target_iqn, config):
target_config = config['targets'][target_iqn]
portals = []
for host in target_config['portals'].keys():
ips = IscsiClient.instance(gateway_name=host).get_ip_addresses()['data']
portal_ips = [ip for ip in ips if ip in target_config['ip_list']]
for portal_ip in portal_ips:
portal = {
'host': host,
'ip': portal_ip
}
portals.append(portal)
portals = IscsiTarget._sorted_portals(portals)
disks = []
for target_disk in target_config['disks']:
disk_config = config['disks'][target_disk]
disk = {
'pool': disk_config['pool'],
'image': disk_config['image'],
'controls': disk_config['controls'],
}
disks.append(disk)
disks = IscsiTarget._sorted_disks(disks)
clients = []
for client_iqn, client_config in target_config['clients'].items():
luns = []
for client_lun in client_config['luns'].keys():
pool, image = client_lun.split('.', 1)
lun = {
'pool': pool,
'image': image
}
luns.append(lun)
user = None
password = None
if '/' in client_config['auth']['chap']:
user, password = client_config['auth']['chap'].split('/', 1)
mutual_user = None
mutual_password = None
if '/' in client_config['auth']['chap_mutual']:
mutual_user, mutual_password = client_config['auth']['chap_mutual'].split('/', 1)
client = {
'client_iqn': client_iqn,
'luns': luns,
'auth': {
'user': user,
'password': password,
'mutual_user': mutual_user,
'mutual_password': mutual_password
}
}
clients.append(client)
clients = IscsiTarget._sorted_clients(clients)
groups = []
for group_id, group_config in target_config['groups'].items():
group_disks = []
for group_disk_key, _ in group_config['disks'].items():
pool, image = group_disk_key.split('.', 1)
group_disk = {
'pool': pool,
'image': image
}
group_disks.append(group_disk)
group = {
'group_id': group_id,
'disks': group_disks,
'members': group_config['members'],
}
groups.append(group)
groups = IscsiTarget._sorted_groups(groups)
target_controls = target_config['controls']
for key, value in target_controls.items():
if isinstance(value, bool):
target_controls[key] = 'Yes' if value else 'No'
target = {
'target_iqn': target_iqn,
'portals': portals,
'disks': disks,
'clients': clients,
'groups': groups,
'target_controls': target_controls,
}
return target
@staticmethod
def _sorted_portals(portals):
portals = portals or []
return sorted(portals, key=lambda p: '{}.{}'.format(p['host'], p['ip']))
@staticmethod
def _sorted_disks(disks):
disks = disks or []
return sorted(disks, key=lambda d: '{}.{}'.format(d['pool'], d['image']))
@staticmethod
def _sorted_clients(clients):
clients = clients or []
for client in clients:
client['luns'] = sorted(client['luns'],
key=lambda d: '{}.{}'.format(d['pool'], d['image']))
return sorted(clients, key=lambda c: c['client_iqn'])
@staticmethod
def _sorted_groups(groups):
groups = groups or []
for group in groups:
group['disks'] = sorted(group['disks'],
key=lambda d: '{}.{}'.format(d['pool'], d['image']))
group['members'] = sorted(group['members'])
return sorted(groups, key=lambda g: g['group_id'])
@staticmethod
def _get_portals_by_host(portals):
portals_by_host = {}
for portal in portals:
host = portal['host']
ip = portal['ip']
if host not in portals_by_host:
portals_by_host[host] = []
portals_by_host[host].append(ip)
return portals_by_host

View File

@ -311,6 +311,16 @@ export class TaskMessageService {
this.commonOperations.delete,
this.rbd_mirroring.pool_peer,
(metadata) => ({})
),
// iSCSI target tasks
'iscsi/target/create': this.newTaskMessage(this.commonOperations.create, (metadata) =>
this.iscsiTarget(metadata)
),
'iscsi/target/edit': this.newTaskMessage(this.commonOperations.update, (metadata) =>
this.iscsiTarget(metadata)
),
'iscsi/target/delete': this.newTaskMessage(this.commonOperations.delete, (metadata) =>
this.iscsiTarget(metadata)
)
};
@ -332,6 +342,10 @@ export class TaskMessageService {
return this.i18n(`erasure code profile '{{name}}'`, { name: metadata.name });
}
iscsiTarget(metadata) {
return this.i18n(`target '{{target_iqn}}'`, { target_iqn: metadata.target_iqn });
}
_getTaskTitle(task: Task) {
return this.messages[task.name] || this.defaultMessage;
}

View File

@ -5387,6 +5387,13 @@
<context context-type="linenumber">1</context>
</context-group>
</trans-unit>
<trans-unit id="369462e5e018360e0600bb570866201ad5c3c8a8" datatype="html">
<source>target &apos;<x id="INTERPOLATION" equiv-text="{{target_iqn}}"/>&apos;</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/shared/services/task-message.service.ts</context>
<context context-type="linenumber">1</context>
</context-group>
</trans-unit>
</body>
</file>
</xliff>

View File

@ -19,6 +19,10 @@ from OpenSSL import crypto
from mgr_module import MgrModule, MgrStandbyModule
# Imports required for CLI commands registration
# pylint: disable=unused-import
from .services import iscsi_cli
try:
import cherrypy
from cherrypy._cptools import HandlerWrapperTool

View File

@ -0,0 +1,131 @@
# -*- coding: utf-8 -*-
from __future__ import absolute_import
import errno
import json
try:
from urlparse import urlparse
except ImportError:
from urllib.parse import urlparse
from mgr_module import CLIReadCommand, CLIWriteCommand
from .orchestrator import OrchClient
from .. import mgr
class IscsiGatewayAlreadyExists(Exception):
def __init__(self, gateway_name):
super(IscsiGatewayAlreadyExists, self).__init__(
"iSCSI gateway '{}' already exists".format(gateway_name))
class IscsiGatewayDoesNotExist(Exception):
def __init__(self, hostname):
super(IscsiGatewayDoesNotExist, self).__init__(
"iSCSI gateway '{}' does not exist".format(hostname))
class InvalidServiceUrl(Exception):
def __init__(self, service_url):
super(InvalidServiceUrl, self).__init__(
"Invalid service URL '{}'. "
"Valid format: '<scheme>://<username>:<password>@<host>[:port]'.".format(service_url))
class ManagedByOrchestratorException(Exception):
def __init__(self):
super(ManagedByOrchestratorException, self).__init__(
"iSCSI configuration is managed by the orchestrator")
_ISCSI_STORE_KEY = "_iscsi_config"
class IscsiGatewaysConfig(object):
@classmethod
def _load_config(cls):
if OrchClient.instance().available():
raise ManagedByOrchestratorException()
json_db = mgr.get_store(_ISCSI_STORE_KEY,
'{"gateways": {}}')
return json.loads(json_db)
@classmethod
def _save_config(cls, config):
mgr.set_store(_ISCSI_STORE_KEY, json.dumps(config))
@classmethod
def add_gateway(cls, name, service_url):
config = cls._load_config()
if name in config:
raise IscsiGatewayAlreadyExists(name)
url = urlparse(service_url)
if not url.scheme or not url.hostname or not url.username or not url.password:
raise InvalidServiceUrl(service_url)
config['gateways'][name] = {'service_url': service_url}
cls._save_config(config)
@classmethod
def remove_gateway(cls, name):
config = cls._load_config()
if name not in config['gateways']:
raise IscsiGatewayDoesNotExist(name)
del config['gateways'][name]
cls._save_config(config)
@classmethod
def get_gateways_config(cls):
try:
config = cls._load_config()
except ManagedByOrchestratorException:
config = {'gateways': {}}
instances = OrchClient.instance().list_service_info("iscsi")
for instance in instances:
config['gateways'][instance.nodename] = {
'service_url': instance.service_url
}
return config
@classmethod
def get_gateway_config(cls, name):
config = IscsiGatewaysConfig.get_gateways_config()
if name not in config['gateways']:
raise IscsiGatewayDoesNotExist(name)
return config['gateways'][name]
@CLIReadCommand('dashboard iscsi-gateway-list', desc='List iSCSI gateways')
def list_iscsi_gateways(_):
return 0, json.dumps(IscsiGatewaysConfig.get_gateways_config()), ''
@CLIWriteCommand('dashboard iscsi-gateway-add',
'name=name,type=CephString '
'name=service_url,type=CephString',
'Add iSCSI gateway configuration')
def add_iscsi_gateway(_, name, service_url):
try:
IscsiGatewaysConfig.add_gateway(name, service_url)
return 0, 'Success', ''
except IscsiGatewayAlreadyExists as ex:
return -errno.EEXIST, '', str(ex)
except InvalidServiceUrl as ex:
return -errno.EINVAL, '', str(ex)
except ManagedByOrchestratorException as ex:
return -errno.EINVAL, '', str(ex)
@CLIWriteCommand('dashboard iscsi-gateway-rm',
'name=name,type=CephString',
'Remove iSCSI gateway configuration')
def remove_iscsi_gateway(_, name):
try:
IscsiGatewaysConfig.remove_gateway(name)
return 0, 'Success', ''
except IscsiGatewayDoesNotExist as ex:
return -errno.ENOENT, '', str(ex)
except ManagedByOrchestratorException as ex:
return -errno.EINVAL, '', str(ex)

View File

@ -0,0 +1,165 @@
# -*- coding: utf-8 -*-
from __future__ import absolute_import
import json
from requests.auth import HTTPBasicAuth
try:
from urlparse import urlparse
except ImportError:
from urllib.parse import urlparse
from .iscsi_cli import IscsiGatewaysConfig
from .. import logger
from ..rest_client import RestClient
class IscsiClient(RestClient):
_CLIENT_NAME = 'iscsi'
_instances = {}
service_url = None
@classmethod
def instance(cls, gateway_name=None):
if not gateway_name:
gateway_name = list(IscsiGatewaysConfig.get_gateways_config()['gateways'].keys())[0]
gateways_config = IscsiGatewaysConfig.get_gateway_config(gateway_name)
service_url = gateways_config['service_url']
instance = cls._instances.get(gateway_name)
if not instance or service_url != instance.service_url:
url = urlparse(service_url)
ssl = url.scheme == 'https'
host = url.hostname
port = url.port
username = url.username
password = url.password
if not port:
port = 443 if ssl else 80
auth = HTTPBasicAuth(username, password)
instance = IscsiClient(host, port, IscsiClient._CLIENT_NAME, ssl, auth)
instance.service_url = service_url
cls._instances[gateway_name] = instance
return instance
@RestClient.api_get('/api/_ping')
def ping(self, request=None):
return request()
@RestClient.api_get('/api/settings')
def get_settings(self, request=None):
return request()
@RestClient.api_get('/api/sysinfo/ip_addresses')
def get_ip_addresses(self, request=None):
return request()
@RestClient.api_get('/api/config')
def get_config(self, request=None):
return request()
@RestClient.api_put('/api/target/{target_iqn}')
def create_target(self, target_iqn, target_controls, request=None):
logger.debug("iSCSI: Creating target: %s", target_iqn)
return request({
'controls': json.dumps(target_controls)
})
@RestClient.api_delete('/api/target/{target_iqn}')
def delete_target(self, target_iqn, request=None):
logger.debug("iSCSI: Deleting target: %s", target_iqn)
return request()
@RestClient.api_put('/api/target/{target_iqn}')
def reconfigure_target(self, target_iqn, target_controls, request=None):
logger.debug("iSCSI: Reconfiguring target: %s", target_iqn)
return request({
'mode': 'reconfigure',
'controls': json.dumps(target_controls)
})
@RestClient.api_put('/api/gateway/{target_iqn}/{gateway_name}')
def create_gateway(self, target_iqn, gateway_name, ip_address, request=None):
logger.debug("iSCSI: Creating gateway: %s/%s", target_iqn, gateway_name)
return request({
'ip_address': ','.join(ip_address),
'skipchecks': 'true'
})
@RestClient.api_put('/api/disk/{image_id}')
def create_disk(self, image_id, request=None):
logger.debug("iSCSI: Creating disk: %s", image_id)
return request({
'mode': 'create'
})
@RestClient.api_delete('/api/disk/{image_id}')
def delete_disk(self, image_id, request=None):
logger.debug("iSCSI: Deleting disk: %s", image_id)
return request({
'preserve_image': 'true'
})
@RestClient.api_put('/api/disk/{image_id}')
def reconfigure_disk(self, image_id, controls, request=None):
logger.debug("iSCSI: Reconfiguring disk: %s", image_id)
return request({
'controls': json.dumps(controls),
'mode': 'reconfigure'
})
@RestClient.api_put('/api/targetlun/{target_iqn}')
def create_target_lun(self, target_iqn, image_id, request=None):
logger.debug("iSCSI: Creating target lun: %s/%s", target_iqn, image_id)
return request({
'disk': image_id
})
@RestClient.api_delete('/api/targetlun/{target_iqn}')
def delete_target_lun(self, target_iqn, image_id, request=None):
logger.debug("iSCSI: Deleting target lun: %s/%s", target_iqn, image_id)
return request({
'disk': image_id
})
@RestClient.api_put('/api/client/{target_iqn}/{client_iqn}')
def create_client(self, target_iqn, client_iqn, request=None):
logger.debug("iSCSI: Creating client: %s/%s", target_iqn, client_iqn)
return request()
@RestClient.api_delete('/api/client/{target_iqn}/{client_iqn}')
def delete_client(self, target_iqn, client_iqn, request=None):
logger.debug("iSCSI: Deleting client: %s/%s", target_iqn, client_iqn)
return request()
@RestClient.api_put('/api/clientlun/{target_iqn}/{client_iqn}')
def create_client_lun(self, target_iqn, client_iqn, image_id, request=None):
logger.debug("iSCSI: Creating client lun: %s/%s", target_iqn, client_iqn)
return request({
'disk': image_id
})
@RestClient.api_put('/api/clientauth/{target_iqn}/{client_iqn}')
def create_client_auth(self, target_iqn, client_iqn, chap, chap_mutual, request=None):
logger.debug("iSCSI: Creating client auth: %s/%s/%s/%s",
target_iqn, client_iqn, chap, chap_mutual)
return request({
'chap': chap,
'chap_mutual': chap_mutual
})
@RestClient.api_put('/api/hostgroup/{target_iqn}/{group_name}')
def create_group(self, target_iqn, group_name, members, image_ids, request=None):
logger.debug("iSCSI: Creating group: %s/%s", target_iqn, group_name)
return request({
'members': ','.join(members),
'disks': ','.join(image_ids)
})
@RestClient.api_delete('/api/hostgroup/{target_iqn}/{group_name}')
def delete_group(self, target_iqn, group_name, request=None):
logger.debug("iSCSI: Deleting group: %s/%s", target_iqn, group_name)
return request()

View File

@ -0,0 +1,55 @@
# -*- coding: utf-8 -*-
from __future__ import absolute_import
import time
from .. import mgr, logger
from ..settings import Settings
class NoOrchesrtatorConfiguredException(Exception):
pass
class OrchClient(object):
_instance = None
@classmethod
def instance(cls):
if cls._instance is None:
cls._instance = OrchClient()
return cls._instance
def _call(self, method, *args, **kwargs):
if not Settings.ORCHESTRATOR_BACKEND:
raise NoOrchesrtatorConfiguredException()
return mgr.remote(Settings.ORCHESTRATOR_BACKEND, method, *args,
**kwargs)
def _wait(self, completions):
while not self._call("wait", completions):
if any(c.should_wait for c in completions):
time.sleep(5)
else:
break
def list_service_info(self, service_type):
completion = self._call("describe_service", service_type, None, None)
self._wait([completion])
return completion.result
def available(self):
if not Settings.ORCHESTRATOR_BACKEND:
return False
status, desc = self._call("available")
logger.info("[ORCH] is orchestrator available: %s, %s", status, desc)
return status
def reload_service(self, service_type, service_ids):
if not isinstance(service_ids, list):
service_ids = [service_ids]
completion_list = [self._call("update_stateless_service", service_type,
service_id, None)
for service_id in service_ids]
self._wait(completion_list)

View File

@ -40,6 +40,9 @@ class Options(object):
GRAFANA_API_USERNAME = ('admin', str)
GRAFANA_API_PASSWORD = ('admin', str)
# Orchestrator settings
ORCHESTRATOR_BACKEND = ('', str)
@staticmethod
def has_default_value(name):
return getattr(Settings, name, None) is None or \

View File

@ -0,0 +1,508 @@
import copy
import mock
from .helper import ControllerTestCase
from .. import mgr
from ..controllers.iscsi import IscsiTarget
from ..services.iscsi_client import IscsiClient
class IscsiTest(ControllerTestCase):
@classmethod
def setup_server(cls):
mgr.rados.side_effect = None
# pylint: disable=protected-access
IscsiTarget._cp_config['tools.authenticate.on'] = False
cls.setup_controllers([IscsiTarget])
def setUp(self):
# pylint: disable=protected-access
IscsiClientMock._instance = IscsiClientMock()
IscsiClient.instance = IscsiClientMock.instance
def test_list_empty(self):
self._get('/api/iscsi/target')
self.assertStatus(200)
self.assertJsonBody([])
@mock.patch('dashboard.controllers.iscsi.IscsiTarget._validate_image_exists')
def test_list(self, _validate_image_exists_mock):
target_iqn = "iqn.2003-01.com.redhat.iscsi-gw:iscsi-igw1"
request = copy.deepcopy(iscsi_target_request)
request['target_iqn'] = target_iqn
self._post('/api/iscsi/target', request)
self.assertStatus(201)
self._get('/api/iscsi/target')
self.assertStatus(200)
response = copy.deepcopy(iscsi_target_response)
response['target_iqn'] = target_iqn
self.assertJsonBody([response])
@mock.patch('dashboard.controllers.iscsi.IscsiTarget._validate_image_exists')
def test_create(self, _validate_image_exists_mock):
target_iqn = "iqn.2003-01.com.redhat.iscsi-gw:iscsi-igw2"
request = copy.deepcopy(iscsi_target_request)
request['target_iqn'] = target_iqn
self._post('/api/iscsi/target', request)
self.assertStatus(201)
self._get('/api/iscsi/target/{}'.format(request['target_iqn']))
self.assertStatus(200)
response = copy.deepcopy(iscsi_target_response)
response['target_iqn'] = target_iqn
self.assertJsonBody(response)
@mock.patch('dashboard.controllers.iscsi.IscsiTarget._validate_image_exists')
def test_delete(self, _validate_image_exists_mock):
target_iqn = "iqn.2003-01.com.redhat.iscsi-gw:iscsi-igw3"
request = copy.deepcopy(iscsi_target_request)
request['target_iqn'] = target_iqn
self._post('/api/iscsi/target', request)
self.assertStatus(201)
self._delete('/api/iscsi/target/{}'.format(request['target_iqn']))
self.assertStatus(204)
self._get('/api/iscsi/target')
self.assertStatus(200)
self.assertJsonBody([])
@mock.patch('dashboard.controllers.iscsi.IscsiTarget._validate_image_exists')
def test_add_client(self, _validate_image_exists_mock):
target_iqn = "iqn.2003-01.com.redhat.iscsi-gw:iscsi-igw4"
create_request = copy.deepcopy(iscsi_target_request)
create_request['target_iqn'] = target_iqn
update_request = copy.deepcopy(create_request)
update_request['new_target_iqn'] = target_iqn
update_request['clients'].append(
{
"luns": [{"image": "lun1", "pool": "rbd"}],
"client_iqn": "iqn.1994-05.com.redhat:rh7-client3",
"auth": {
"password": "myiscsipassword5",
"user": "myiscsiusername5",
"mutual_password": "myiscsipassword6",
"mutual_user": "myiscsiusername6"}
})
response = copy.deepcopy(iscsi_target_response)
response['target_iqn'] = target_iqn
response['clients'].append(
{
"luns": [{"image": "lun1", "pool": "rbd"}],
"client_iqn": "iqn.1994-05.com.redhat:rh7-client3",
"auth": {
"password": "myiscsipassword5",
"user": "myiscsiusername5",
"mutual_password": "myiscsipassword6",
"mutual_user": "myiscsiusername6"}
})
self._update_iscsi_target(create_request, update_request, response)
@mock.patch('dashboard.controllers.iscsi.IscsiTarget._validate_image_exists')
def test_change_client_password(self, _validate_image_exists_mock):
target_iqn = "iqn.2003-01.com.redhat.iscsi-gw:iscsi-igw5"
create_request = copy.deepcopy(iscsi_target_request)
create_request['target_iqn'] = target_iqn
update_request = copy.deepcopy(create_request)
update_request['new_target_iqn'] = target_iqn
update_request['clients'][0]['auth']['password'] = 'mynewiscsipassword'
response = copy.deepcopy(iscsi_target_response)
response['target_iqn'] = target_iqn
response['clients'][0]['auth']['password'] = 'mynewiscsipassword'
self._update_iscsi_target(create_request, update_request, response)
@mock.patch('dashboard.controllers.iscsi.IscsiTarget._validate_image_exists')
def test_rename_client(self, _validate_image_exists_mock):
target_iqn = "iqn.2003-01.com.redhat.iscsi-gw:iscsi-igw6"
create_request = copy.deepcopy(iscsi_target_request)
create_request['target_iqn'] = target_iqn
update_request = copy.deepcopy(create_request)
update_request['new_target_iqn'] = target_iqn
update_request['clients'][0]['client_iqn'] = 'iqn.1994-05.com.redhat:rh7-client0'
response = copy.deepcopy(iscsi_target_response)
response['target_iqn'] = target_iqn
response['clients'][0]['client_iqn'] = 'iqn.1994-05.com.redhat:rh7-client0'
self._update_iscsi_target(create_request, update_request, response)
@mock.patch('dashboard.controllers.iscsi.IscsiTarget._validate_image_exists')
def test_add_disk(self, _validate_image_exists_mock):
target_iqn = "iqn.2003-01.com.redhat.iscsi-gw:iscsi-igw7"
create_request = copy.deepcopy(iscsi_target_request)
create_request['target_iqn'] = target_iqn
update_request = copy.deepcopy(create_request)
update_request['new_target_iqn'] = target_iqn
update_request['disks'].append(
{
"image": "lun3",
"pool": "rbd",
"controls": {}
})
update_request['clients'][0]['luns'].append({"image": "lun3", "pool": "rbd"})
response = copy.deepcopy(iscsi_target_response)
response['target_iqn'] = target_iqn
response['disks'].append(
{
"image": "lun3",
"pool": "rbd",
"controls": {}
})
response['clients'][0]['luns'].append({"image": "lun3", "pool": "rbd"})
self._update_iscsi_target(create_request, update_request, response)
@mock.patch('dashboard.controllers.iscsi.IscsiTarget._validate_image_exists')
def test_change_disk_image(self, _validate_image_exists_mock):
target_iqn = "iqn.2003-01.com.redhat.iscsi-gw:iscsi-igw8"
create_request = copy.deepcopy(iscsi_target_request)
create_request['target_iqn'] = target_iqn
update_request = copy.deepcopy(create_request)
update_request['new_target_iqn'] = target_iqn
update_request['disks'][0]['image'] = 'lun0'
update_request['clients'][0]['luns'][0]['image'] = 'lun0'
response = copy.deepcopy(iscsi_target_response)
response['target_iqn'] = target_iqn
response['disks'][0]['image'] = 'lun0'
response['clients'][0]['luns'][0]['image'] = 'lun0'
self._update_iscsi_target(create_request, update_request, response)
@mock.patch('dashboard.controllers.iscsi.IscsiTarget._validate_image_exists')
def test_change_disk_controls(self, _validate_image_exists_mock):
target_iqn = "iqn.2003-01.com.redhat.iscsi-gw:iscsi-igw9"
create_request = copy.deepcopy(iscsi_target_request)
create_request['target_iqn'] = target_iqn
update_request = copy.deepcopy(create_request)
update_request['new_target_iqn'] = target_iqn
update_request['disks'][0]['controls'] = {"qfull_timeout": 15}
response = copy.deepcopy(iscsi_target_response)
response['target_iqn'] = target_iqn
response['disks'][0]['controls'] = {"qfull_timeout": 15}
self._update_iscsi_target(create_request, update_request, response)
@mock.patch('dashboard.controllers.iscsi.IscsiTarget._validate_image_exists')
def test_rename_target(self, _validate_image_exists_mock):
target_iqn = "iqn.2003-01.com.redhat.iscsi-gw:iscsi-igw10"
new_target_iqn = "iqn.2003-01.com.redhat.iscsi-gw:iscsi-igw11"
create_request = copy.deepcopy(iscsi_target_request)
create_request['target_iqn'] = target_iqn
update_request = copy.deepcopy(create_request)
update_request['new_target_iqn'] = new_target_iqn
response = copy.deepcopy(iscsi_target_response)
response['target_iqn'] = new_target_iqn
self._update_iscsi_target(create_request, update_request, response)
@mock.patch('dashboard.controllers.iscsi.IscsiTarget._validate_image_exists')
def test_rename_group(self, _validate_image_exists_mock):
target_iqn = "iqn.2003-01.com.redhat.iscsi-gw:iscsi-igw12"
create_request = copy.deepcopy(iscsi_target_request)
create_request['target_iqn'] = target_iqn
update_request = copy.deepcopy(create_request)
update_request['new_target_iqn'] = target_iqn
update_request['groups'][0]['group_id'] = 'mygroup0'
response = copy.deepcopy(iscsi_target_response)
response['target_iqn'] = target_iqn
response['groups'][0]['group_id'] = 'mygroup0'
self._update_iscsi_target(create_request, update_request, response)
@mock.patch('dashboard.controllers.iscsi.IscsiTarget._validate_image_exists')
def test_add_client_to_group(self, _validate_image_exists_mock):
target_iqn = "iqn.2003-01.com.redhat.iscsi-gw:iscsi-igw13"
create_request = copy.deepcopy(iscsi_target_request)
create_request['target_iqn'] = target_iqn
update_request = copy.deepcopy(create_request)
update_request['new_target_iqn'] = target_iqn
update_request['clients'].append(
{
"luns": [],
"client_iqn": "iqn.1994-05.com.redhat:rh7-client3",
"auth": {
"password": None,
"user": None,
"mutual_password": None,
"mutual_user": None}
})
update_request['groups'][0]['members'].append('iqn.1994-05.com.redhat:rh7-client3')
response = copy.deepcopy(iscsi_target_response)
response['target_iqn'] = target_iqn
response['clients'].append(
{
"luns": [],
"client_iqn": "iqn.1994-05.com.redhat:rh7-client3",
"auth": {
"password": None,
"user": None,
"mutual_password": None,
"mutual_user": None}
})
response['groups'][0]['members'].append('iqn.1994-05.com.redhat:rh7-client3')
self._update_iscsi_target(create_request, update_request, response)
@mock.patch('dashboard.controllers.iscsi.IscsiTarget._validate_image_exists')
def test_remove_client_from_group(self, _validate_image_exists_mock):
target_iqn = "iqn.2003-01.com.redhat.iscsi-gw:iscsi-igw14"
create_request = copy.deepcopy(iscsi_target_request)
create_request['target_iqn'] = target_iqn
update_request = copy.deepcopy(create_request)
update_request['new_target_iqn'] = target_iqn
update_request['groups'][0]['members'].remove('iqn.1994-05.com.redhat:rh7-client2')
response = copy.deepcopy(iscsi_target_response)
response['target_iqn'] = target_iqn
response['groups'][0]['members'].remove('iqn.1994-05.com.redhat:rh7-client2')
self._update_iscsi_target(create_request, update_request, response)
@mock.patch('dashboard.controllers.iscsi.IscsiTarget._validate_image_exists')
def test_remove_groups(self, _validate_image_exists_mock):
target_iqn = "iqn.2003-01.com.redhat.iscsi-gw:iscsi-igw15"
create_request = copy.deepcopy(iscsi_target_request)
create_request['target_iqn'] = target_iqn
update_request = copy.deepcopy(create_request)
update_request['new_target_iqn'] = target_iqn
update_request['groups'] = []
response = copy.deepcopy(iscsi_target_response)
response['target_iqn'] = target_iqn
response['groups'] = []
self._update_iscsi_target(create_request, update_request, response)
def _update_iscsi_target(self, create_request, update_request, response):
self._post('/api/iscsi/target', create_request)
self.assertStatus(201)
self._put('/api/iscsi/target/{}'.format(create_request['target_iqn']), update_request)
self.assertStatus(200)
self._get('/api/iscsi/target/{}'.format(update_request['new_target_iqn']))
self.assertStatus(200)
self.assertJsonBody(response)
iscsi_target_request = {
"target_iqn": "iqn.2003-01.com.redhat.iscsi-gw:iscsi-igw",
"portals": [
{"ip": "192.168.100.202", "host": "node2"},
{"ip": "10.0.2.15", "host": "node2"},
{"ip": "192.168.100.203", "host": "node3"}
],
"disks": [
{"image": "lun1", "pool": "rbd", "controls": {"max_data_area_mb": 128}},
{"image": "lun2", "pool": "rbd", "controls": {"max_data_area_mb": 128}}
],
"clients": [
{
"luns": [{"image": "lun1", "pool": "rbd"}],
"client_iqn": "iqn.1994-05.com.redhat:rh7-client",
"auth": {
"password": "myiscsipassword1",
"user": "myiscsiusername1",
"mutual_password": "myiscsipassword2",
"mutual_user": "myiscsiusername2"}
},
{
"luns": [],
"client_iqn": "iqn.1994-05.com.redhat:rh7-client2",
"auth": {
"password": "myiscsipassword3",
"user": "myiscsiusername3",
"mutual_password": "myiscsipassword4",
"mutual_user": "myiscsiusername4"
}
}
],
"target_controls": {},
"groups": [
{
"group_id": "mygroup",
"disks": [{"pool": "rbd", "image": "lun2"}],
"members": ["iqn.1994-05.com.redhat:rh7-client2"]
}
]
}
iscsi_target_response = {
'target_iqn': 'iqn.2003-01.com.redhat.iscsi-gw:iscsi-igw',
'portals': [
{'host': 'node2', 'ip': '10.0.2.15'},
{'host': 'node2', 'ip': '192.168.100.202'},
{'host': 'node3', 'ip': '192.168.100.203'}
],
'disks': [
{'pool': 'rbd', 'image': 'lun1', 'controls': {'max_data_area_mb': 128}},
{'pool': 'rbd', 'image': 'lun2', 'controls': {'max_data_area_mb': 128}}
],
'clients': [
{
'client_iqn': 'iqn.1994-05.com.redhat:rh7-client',
'luns': [{'pool': 'rbd', 'image': 'lun1'}],
'auth': {
'user': 'myiscsiusername1',
'password': 'myiscsipassword1',
'mutual_password': 'myiscsipassword2',
'mutual_user': 'myiscsiusername2'
}
},
{
'client_iqn': 'iqn.1994-05.com.redhat:rh7-client2',
'luns': [],
'auth': {
'user': 'myiscsiusername3',
'password': 'myiscsipassword3',
'mutual_password': 'myiscsipassword4',
'mutual_user': 'myiscsiusername4'
}
}
],
'groups': [
{
'group_id': 'mygroup',
'disks': [{'pool': 'rbd', 'image': 'lun2'}],
'members': ['iqn.1994-05.com.redhat:rh7-client2']
}
],
'target_controls': {}
}
class IscsiClientMock(object):
_instance = None
def __init__(self):
self.gateway_name = None
self.config = {
"created": "2019/01/17 08:57:16",
"discovery_auth": {
"chap": "",
"chap_mutual": ""
},
"disks": {},
"epoch": 0,
"gateways": {},
"targets": {},
"updated": "",
"version": 4
}
@classmethod
def instance(cls, gateway_name=None):
cls._instance.gateway_name = gateway_name
# pylint: disable=unused-argument
return cls._instance
def ping(self):
return {
"message": "pong"
}
def get_settings(self):
return {
"config": {
"minimum_gateways": 2
},
"disk_default_controls": {
"hw_max_sectors": 1024,
"max_data_area_mb": 8,
"osd_op_timeout": 30,
"qfull_timeout": 5
},
"target_default_controls": {
"cmdsn_depth": 128,
"dataout_timeout": 20,
"first_burst_length": 262144,
"immediate_data": "Yes",
"initial_r2t": "Yes",
"max_burst_length": 524288,
"max_outstanding_r2t": 1,
"max_recv_data_segment_length": 262144,
"max_xmit_data_segment_length": 262144,
"nopin_response_timeout": 5,
"nopin_timeout": 5
}
}
def get_config(self):
return self.config
def create_target(self, target_iqn, target_controls):
self.config['targets'][target_iqn] = {
"clients": {},
"controls": target_controls,
"created": "2019/01/17 09:22:34",
"disks": [],
"groups": {},
"portals": {}
}
def create_gateway(self, target_iqn, gateway_name, ip_address):
target_config = self.config['targets'][target_iqn]
if 'ip_list' not in target_config:
target_config['ip_list'] = []
target_config['ip_list'] += ip_address
target_config['portals'][gateway_name] = {
"portal_ip_address": ip_address[0]
}
def create_disk(self, image_id):
pool, image = image_id.split('.')
self.config['disks'][image_id] = {
"pool": pool,
"image": image,
"controls": {}
}
def create_target_lun(self, target_iqn, image_id):
target_config = self.config['targets'][target_iqn]
target_config['disks'].append(image_id)
self.config['disks'][image_id]['owner'] = list(target_config['portals'].keys())[0]
def reconfigure_disk(self, image_id, controls):
self.config['disks'][image_id]['controls'] = controls
def create_client(self, target_iqn, client_iqn):
target_config = self.config['targets'][target_iqn]
target_config['clients'][client_iqn] = {
"auth": {
"chap": "",
"chap_mutual": ""
},
"group_name": "",
"luns": {}
}
def create_client_lun(self, target_iqn, client_iqn, image_id):
target_config = self.config['targets'][target_iqn]
target_config['clients'][client_iqn]['luns'][image_id] = {}
def create_client_auth(self, target_iqn, client_iqn, chap, chap_mutual):
target_config = self.config['targets'][target_iqn]
target_config['clients'][client_iqn]['auth']['chap'] = chap
target_config['clients'][client_iqn]['auth']['chap_mutual'] = chap_mutual
def create_group(self, target_iqn, group_name, members, image_ids):
target_config = self.config['targets'][target_iqn]
target_config['groups'][group_name] = {
"disks": {},
"members": []
}
for image_id in image_ids:
target_config['groups'][group_name]['disks'][image_id] = {}
target_config['groups'][group_name]['members'] = members
def delete_group(self, target_iqn, group_name):
target_config = self.config['targets'][target_iqn]
del target_config['groups'][group_name]
def delete_client(self, target_iqn, client_iqn):
target_config = self.config['targets'][target_iqn]
del target_config['clients'][client_iqn]
def delete_target_lun(self, target_iqn, image_id):
target_config = self.config['targets'][target_iqn]
target_config['disks'].remove(image_id)
del self.config['disks'][image_id]['owner']
def delete_disk(self, image_id):
del self.config['disks'][image_id]
def delete_target(self, target_iqn):
del self.config['targets'][target_iqn]
def get_ip_addresses(self):
ips = {
'node1': ['192.168.100.201'],
'node2': ['192.168.100.202', '10.0.2.15'],
'node3': ['192.168.100.203']
}
return {'data': ips[self.gateway_name]}