Add module and states for Ironic API v1
Change-Id: I832381955e608875e87680211e8e7a3836facb40
Related-Prod: PROD-21813
diff --git a/_modules/ironicv1/__init__.py b/_modules/ironicv1/__init__.py
new file mode 100644
index 0000000..ece8bac
--- /dev/null
+++ b/_modules/ironicv1/__init__.py
@@ -0,0 +1,112 @@
+try:
+ import os_client_config
+ from keystoneauth1 import exceptions as ka_exceptions
+ REQUIREMENTS_MET = True
+except ImportError:
+ REQUIREMENTS_MET = False
+
+from ironicv1 import nodes
+from ironicv1 import ports
+from ironicv1 import drivers
+from ironicv1 import chassis
+from ironicv1 import volumes
+
+node_boot_device_get = nodes.node_boot_device_get
+node_boot_device_get_supported = nodes.node_boot_device_get_supported
+node_boot_device_set = nodes.node_boot_device_set
+node_console_get = nodes.node_console_get
+node_console_start_stop = nodes.node_console_start_stop
+node_create = nodes.node_create
+node_delete = nodes.node_delete
+node_get_details = nodes.node_get_details
+node_inject_nmi = nodes.node_inject_nmi
+node_list = nodes.node_list
+node_maintenance_flag_clear = nodes.node_maintenance_flag_clear
+node_maintenance_flag_set = nodes.node_maintenance_flag_set
+node_power_state_change = nodes.node_power_state_change
+node_provision_state_change = nodes.node_provision_state_change
+node_raid_config_set = nodes.node_raid_config_set
+node_state_summary = nodes.node_state_summary
+node_traits_delete = nodes.node_traits_delete
+node_traits_delete_single = nodes.node_traits_delete_single
+node_traits_list = nodes.node_traits_list
+node_traits_set = nodes.node_traits_set
+node_traits_set_single = nodes.node_traits_set_single
+node_update = nodes.node_update
+node_validate = nodes.node_validate
+node_vif_attach = nodes.node_vif_attach
+node_vif_detach = nodes.node_vif_detach
+node_vif_list = nodes.node_vif_list
+
+driver_get_details = drivers.driver_get_details
+driver_get_logical_disk_properties = drivers.driver_get_logical_disk_properties
+driver_get_properties = drivers.driver_get_properties
+driver_list = drivers.driver_list
+
+port_create = ports.port_create
+port_delete = ports.port_delete
+port_get_details = ports.port_get_details
+port_list = ports.port_list
+port_list_details = ports.port_list_details
+port_update = ports.port_update
+
+chassis_create = chassis.chassis_create
+chassis_delete = chassis.chassis_delete
+chassis_get_details = chassis.chassis_get_details
+chassis_list = chassis.chassis_list
+chassis_list_details = chassis.chassis_list_details
+chassis_update = chassis.chassis_update
+
+volume_connector_create = volumes.volume_connector_create
+volume_connector_delete = volumes.volume_connector_delete
+volume_connector_get_details = volumes.volume_connector_get_details
+volume_connector_list = volumes.volume_connector_list
+volume_connector_update = volumes.volume_connector_update
+volume_resource_list = volumes.volume_resource_list
+volume_target_create = volumes.volume_target_create
+volume_target_delete = volumes.volume_target_delete
+volume_target_get_details = volumes.volume_target_get_details
+volume_target_list = volumes.volume_target_list
+volume_target_update = volumes.volume_target_update
+
+
+__all__ = (
+ # node.py
+ 'node_list', 'node_boot_device_get', 'node_boot_device_get_supported',
+ 'node_boot_device_set', 'node_console_get', 'node_console_start_stop',
+ 'node_create', 'node_delete', 'node_get_details', 'node_inject_nmi',
+ 'node_maintenance_flag_clear', 'node_maintenance_flag_set',
+ 'node_power_state_change', 'node_provision_state_change',
+ 'node_raid_config_set', 'node_state_summary', 'node_traits_delete',
+ 'node_traits_delete_single', 'node_traits_list', 'node_traits_set',
+ 'node_traits_set_single', 'node_update', 'node_validate',
+ 'node_vif_attach', 'node_vif_detach', 'node_vif_list',
+
+ # driver.py
+ 'driver_get_details', 'driver_get_logical_disk_properties',
+ 'driver_get_properties', 'driver_list',
+
+ # ports.py
+ 'port_create', 'port_delete', 'port_get_details', 'port_list',
+ 'port_update', 'port_list_details',
+
+ # chassis.py
+ 'chassis_create', 'chassis_delete', 'chassis_get_details', 'chassis_list',
+ 'chassis_list_details', 'chassis_update',
+
+ # volumes.py
+ 'volume_connector_create', 'volume_connector_delete',
+ 'volume_connector_get_details', 'volume_connector_list',
+ 'volume_connector_update', 'volume_resource_list', 'volume_target_create',
+ 'volume_target_delete', 'volume_target_get_details', 'volume_target_list',
+ 'volume_target_update',
+)
+
+
+def __virtual__():
+ """Only load ironicv1 if requirements are available."""
+ if REQUIREMENTS_MET:
+ return 'ironicv1'
+ else:
+ return False, ("The ironicv1 execution module cannot be loaded: "
+ "os_client_config or keystoneauth are unavailable.")
diff --git a/_modules/ironicv1/chassis.py b/_modules/ironicv1/chassis.py
new file mode 100644
index 0000000..5d747cf
--- /dev/null
+++ b/_modules/ironicv1/chassis.py
@@ -0,0 +1,41 @@
+from ironicv1.common import send
+try:
+ from urllib.parse import urlencode
+except ImportError:
+ from urllib import urlencode
+
+
+@send('get')
+def chassis_list_details(**kwargs):
+ url = '/chassis/detail?{}'.format(urlencode(kwargs))
+ return url, {}
+
+
+@send('get')
+def chassis_get_details(chassis_id, **kwargs):
+ url = '/chassis/{}?{}'.format(chassis_id, urlencode(kwargs))
+ return url, {}
+
+
+@send('patch')
+def chassis_update(chassis_id, properties, **kwargs):
+ url = '/chassis/{}'.format(chassis_id)
+ return url, {'json': properties}
+
+
+@send('delete')
+def chassis_delete(chassis_id, **kwargs):
+ url = '/chassis/{}'.format(chassis_id)
+ return url, {}
+
+
+@send('post')
+def chassis_create(chassis, **kwargs):
+ url = '/chassis'
+ return url, {'json': chassis}
+
+
+@send('get')
+def chassis_list(**kwargs):
+ url = '/chassis?{}'.format(urlencode(kwargs))
+ return url, {}
diff --git a/_modules/ironicv1/common.py b/_modules/ironicv1/common.py
new file mode 100644
index 0000000..b2a6071
--- /dev/null
+++ b/_modules/ironicv1/common.py
@@ -0,0 +1,77 @@
+import logging
+import os_client_config
+
+log = logging.getLogger(__name__)
+
+IRONIC_VERSION_HEADER = 'X-OpenStack-Ironic-API-Version'
+ADAPTER_VERSION = '1.0'
+
+
+class IronicException(Exception):
+
+ _msg = "Ironic module exception occurred."
+
+ def __init__(self, message=None, **kwargs):
+ super(IronicException, self).__init__(message or self._msg)
+
+
+class NoIronicEndpoint(IronicException):
+ _msg = "Ironic endpoint not found in keystone catalog."
+
+
+class NoAuthPluginConfigured(IronicException):
+ _msg = ("You are using keystoneauth auth plugin that does not support "
+ "fetching endpoint list from token (noauth or admin_token).")
+
+
+class NoCredentials(IronicException):
+ _msg = "Please provide cloud name present in clouds.yaml."
+
+
+def _get_raw_client(cloud_name):
+ service_type = 'baremetal'
+ config = os_client_config.OpenStackConfig()
+ cloud = config.get_one_cloud(cloud_name)
+ adapter = cloud.get_session_client(service_type)
+ adapter.version = ADAPTER_VERSION
+ try:
+ access_info = adapter.session.auth.get_access(adapter.session)
+ access_info.service_catalog.get_endpoints()
+ except (AttributeError, ValueError):
+ e = NoAuthPluginConfigured()
+ log.exception('%s' % e)
+ raise e
+ return adapter
+
+
+def send(method):
+ def wrap(func):
+ def wrapped_f(*args, **kwargs):
+ cloud_name = kwargs.pop('cloud_name')
+ if not cloud_name:
+ e = NoCredentials()
+ log.error('%s' % e)
+ raise e
+ adapter = _get_raw_client(cloud_name)
+ # Remove salt internal kwargs
+ kwarg_keys = list(kwargs.keys())
+ for k in kwarg_keys:
+ if k.startswith('__'):
+ kwargs.pop(k)
+ microversion = kwargs.pop('microversion', None)
+ url, request_kwargs = func(*args, **kwargs)
+ if microversion:
+ if 'headers' not in request_kwargs:
+ request_kwargs['headers'] = {}
+ request_kwargs['headers'][IRONIC_VERSION_HEADER] = \
+ microversion
+ response = getattr(adapter, method)(url, **request_kwargs)
+ if not response.content:
+ return {}
+ try:
+ resp = response.json()
+ except ValueError:
+ resp = response.content
+ return resp
+ return wrapped_f
+ return wrap
diff --git a/_modules/ironicv1/drivers.py b/_modules/ironicv1/drivers.py
new file mode 100644
index 0000000..a4fd0e8
--- /dev/null
+++ b/_modules/ironicv1/drivers.py
@@ -0,0 +1,29 @@
+from ironicv1.common import send
+try:
+ from urllib.parse import urlencode
+except ImportError:
+ from urllib import urlencode
+
+
+@send('get')
+def driver_list(**kwargs):
+ url = '/drivers?{}'.format(urlencode(kwargs))
+ return url, {}
+
+
+@send('get')
+def driver_get_details(name, **kwargs):
+ url = '/drivers/{}'.format(name)
+ return url, {}
+
+
+@send('get')
+def driver_get_properties(name, **kwargs):
+ url = '/drivers/{}/properties'.format(name)
+ return url, {}
+
+
+@send('get')
+def driver_get_logical_disk_properties(name, **kwargs):
+ url = '/drivers/{}/raid/logical_disk_properties'.format(name)
+ return url, {}
diff --git a/_modules/ironicv1/nodes.py b/_modules/ironicv1/nodes.py
new file mode 100644
index 0000000..f47f629
--- /dev/null
+++ b/_modules/ironicv1/nodes.py
@@ -0,0 +1,196 @@
+from ironicv1.common import send
+try:
+ from urllib.parse import urlencode
+except ImportError:
+ from urllib import urlencode
+
+
+# NOTE(opetrenko): Each driver require different driver_info or do not require
+# it at all. To make things work, please use driver_get_properties with driver
+# you want to use to get list of required arguments for driver_info.
+# For more take a look at Baremetal API Reference
+@send('post')
+def node_create(driver, **kwargs):
+ url = '/nodes'
+ json = {
+ 'driver': driver,
+ }
+ json.update(kwargs)
+ return url, {'json': json}
+
+
+@send('get')
+def node_list(**kwargs):
+ url = '/nodes?{}'.format(urlencode(kwargs))
+ return url, {}
+
+
+@send('get')
+def node_get_details(node_ident, **kwargs):
+ url = '/nodes/{}?{}'.format(node_ident, urlencode(kwargs))
+ return url, {}
+
+
+@send('patch')
+def node_update(node_ident, properties, **kwargs):
+ url = '/nodes/{}'.format(node_ident)
+ return url, {'json': properties}
+
+
+@send('delete')
+def node_delete(node_ident, **kwargs):
+ url = '/nodes/{}'.format(node_ident)
+ return url, {}
+
+
+# NOTE: Node management API
+@send('get')
+def node_validate(node_ident, **kwargs):
+ url = '/nodes/{}/validate'.format(node_ident)
+ return url, {}
+
+
+@send('put')
+def node_maintenance_flag_set(node_ident, **kwargs):
+ url = '/nodes/{}/maintenance'.format(node_ident)
+ json = {}
+ if 'reason' in kwargs:
+ json['reason'] = kwargs['reason']
+ return url, {'json': json}
+
+
+@send('delete')
+def node_maintenance_flag_clear(node_ident, **kwargs):
+ url = '/nodes/{}/maintenance'.format(node_ident)
+ return url, {}
+
+
+@send('put')
+def node_boot_device_set(node_ident, boot_device, **kwargs):
+ url = '/nodes/{}/management/boot_device'.format(node_ident)
+ json = {
+ 'boot_device': boot_device,
+ }
+ json.update(kwargs)
+ return url, {'json': json}
+
+
+@send('get')
+def node_boot_device_get(node_ident, **kwargs):
+ url = '/nodes/{}/management/boot_device'.format(node_ident)
+ return url, {}
+
+
+@send('get')
+def node_boot_device_get_supported(node_ident, **kwargs):
+ url = '/nodes/{}/management/boot_device/supported'.format(node_ident)
+ return url, {}
+
+
+@send('put')
+def node_inject_nmi(node_ident, **kwargs):
+ url = '/nodes/{}/management/inject_nmi'.format(node_ident)
+ return url, {'json': {}}
+
+
+@send('get')
+def node_state_summary(node_ident, **kwargs):
+ url = '/nodes/{}/states'.format(node_ident)
+ return url, {}
+
+
+@send('put')
+def node_power_state_change(node_ident, target, **kwargs):
+ url = '/nodes/{}/states/power'.format(node_ident)
+ json = {
+ 'target': target,
+ }
+ json.update(kwargs)
+ return url, {'json': json}
+
+
+@send('put')
+def node_provision_state_change(node_ident, target, **kwargs):
+ url = '/nodes/{}/states/provision'.format(node_ident)
+ json = {
+ 'target': target,
+ }
+ json.update(kwargs)
+ return url, {'json': json}
+
+
+@send('put')
+def node_raid_config_set(node_ident, target_raid_config, **kwargs):
+ url = 'nodes/{}/states/raid'.format(node_ident)
+ return url, {'json': target_raid_config}
+
+
+@send('get')
+def node_console_get(node_ident, **kwargs):
+ url = '/nodes/{}/states/console'.format(node_ident)
+ return url, {}
+
+
+@send('put')
+def node_console_start_stop(node_ident, enabled, **kwargs):
+ url = '/nodes/{}/states/console'.format(node_ident)
+ json = {
+ 'enabled': enabled,
+ }
+ return url, {'json': json}
+
+
+# NOTE: Node Traits API
+@send('get')
+def node_traits_list(node_ident, **kwargs):
+ url = '/nodes/{}/traits'.format(node_ident)
+ return url, {}
+
+
+@send('put')
+def node_traits_set(node_ident, traits, **kwargs):
+ url = '/nodes/{}/traits'.format(node_ident)
+ json = {
+ 'traits': traits,
+ }
+ return url, {'json': json}
+
+
+@send('put')
+def node_traits_set_single(node_ident, trait, **kwargs):
+ url = '/nodes/{}/traits/{}'.format(node_ident, trait)
+ return url, {'json': {}}
+
+
+@send('delete')
+def node_traits_delete(node_ident, **kwargs):
+ url = '/nodes/{}/traits'.format(node_ident)
+ return url, {}
+
+
+@send('delete')
+def node_traits_delete_single(node_ident, trait, **kwargs):
+ url = '/nodes/{}/traits/{}'.format(node_ident, trait)
+ return url, {}
+
+
+# NOTE: VIFs API
+@send('get')
+def node_vif_list(node_ident, **kwargs):
+ url = '/nodes/{}/vifs'.format(node_ident)
+ return url, {}
+
+
+@send('post')
+def node_vif_attach(node_ident, id, **kwargs):
+ url = '/nodes/{}/vifs'.format(node_ident)
+ json = {
+ 'id': id,
+ }
+ return url, {'json': json}
+
+
+@send('delete')
+def node_vif_detach(node_ident, vif_ident, **kwargs):
+ url = '/nodes/{}/vifs/{}'.format(node_ident, vif_ident)
+ return url, {}
diff --git a/_modules/ironicv1/ports.py b/_modules/ironicv1/ports.py
new file mode 100644
index 0000000..5130185
--- /dev/null
+++ b/_modules/ironicv1/ports.py
@@ -0,0 +1,46 @@
+from ironicv1.common import send
+try:
+ from urllib.parse import urlencode
+except ImportError:
+ from urllib import urlencode
+
+
+@send('get')
+def port_list(**kwargs):
+ url = '/ports?{}'.format(urlencode(kwargs))
+ return url, {}
+
+
+@send('post')
+def port_create(node_uuid, address, **kwargs):
+ url = '/ports'
+ json = {
+ 'node_uuid': node_uuid,
+ 'address': address,
+ }
+ json.update(kwargs)
+ return url, {'json': json}
+
+
+@send('get')
+def port_list_details(**kwargs):
+ url = '/ports/detail?{}'.format(urlencode(kwargs))
+ return url, {}
+
+
+@send('get')
+def port_get_details(port_id, **kwargs):
+ url = '/ports/{}?{}'.format(port_id, urlencode(kwargs))
+ return url, {}
+
+
+@send('patch')
+def port_update(port_id, properties, **kwargs):
+ url = '/ports/{}'.format(port_id)
+ return url, {'json': properties}
+
+
+@send('delete')
+def port_delete(port_id, **kwargs):
+ url = '/ports/{}'.format(port_id)
+ return url, {}
\ No newline at end of file
diff --git a/_modules/ironicv1/volumes.py b/_modules/ironicv1/volumes.py
new file mode 100644
index 0000000..9732cad
--- /dev/null
+++ b/_modules/ironicv1/volumes.py
@@ -0,0 +1,87 @@
+from ironicv1.common import send
+try:
+ from urllib.parse import urlencode
+except ImportError:
+ from urllib import urlencode
+
+
+@send('get')
+def volume_resource_list(**kwargs):
+ url = '/volume'
+ return url, {}
+
+
+@send('get')
+def volume_connector_list(**kwargs):
+ url = '/volume/connectors?{}'.format(urlencode(kwargs))
+ return url, {}
+
+
+@send('post')
+def volume_connector_create(node_uuid, volume_type, connector_id, **kwargs):
+ url = '/volume/connectors'
+ json = {
+ 'node_uuid': node_uuid,
+ 'type': volume_type,
+ 'connector_id': connector_id,
+ }
+ json.update(kwargs)
+ return url, {'json': json}
+
+
+@send('get')
+def volume_connector_get_details(volume_connector_id, **kwargs):
+ url = '/volume/connectors/{}?{}'.format(
+ volume_connector_id, urlencode(kwargs))
+ return url, {}
+
+
+@send('patch')
+def volume_connector_update(volume_connector_id, properties, **kwargs):
+ url = '/volume/connectors/{}'.format(volume_connector_id)
+ return url, {'json': properties}
+
+
+@send('delete')
+def volume_connector_delete(volume_connector_id, **kwargs):
+ url = '/volume/connectors/{}'.format(volume_connector_id)
+ return url, {}
+
+
+@send('get')
+def volume_target_list(**kwargs):
+ url = '/volume/targets?{}'.format(urlencode(kwargs))
+ return url, {}
+
+
+@send('post')
+def volume_target_create(node_uuid, volume_type, properties,
+ boot_index, volume_id, **kwargs):
+ url = '/volume/targets'
+ json = {
+ 'node_uuid': node_uuid,
+ 'volume_type': volume_type,
+ 'properties': properties,
+ 'boot_index': boot_index,
+ 'volume_id': volume_id,
+ }
+ json.update(kwargs)
+ return url, {'json': json}
+
+
+@send('get')
+def volume_target_get_details(target_id, **kwargs):
+ url = '/volume/targets/{}?{}'.format(target_id, urlencode(kwargs))
+ return url, {}
+
+
+@send('patch')
+def volume_target_update(target_id, properties, **kwargs):
+ url = '/volume/targets/{}'.format(target_id)
+ return url, {'json': properties}
+
+
+@send('delete')
+def volume_target_delete(target_id, **kwargs):
+ url = '/volume/targets/{}'.format(target_id)
+ return url, {}