summaryrefslogtreecommitdiff
path: root/tuskar_ui/api
diff options
context:
space:
mode:
Diffstat (limited to 'tuskar_ui/api')
-rw-r--r--tuskar_ui/api/__init__.py0
-rw-r--r--tuskar_ui/api/flavor.py121
-rw-r--r--tuskar_ui/api/heat.py553
-rw-r--r--tuskar_ui/api/node.py445
-rw-r--r--tuskar_ui/api/tuskar.py558
5 files changed, 0 insertions, 1677 deletions
diff --git a/tuskar_ui/api/__init__.py b/tuskar_ui/api/__init__.py
deleted file mode 100644
index e69de29b..00000000
--- a/tuskar_ui/api/__init__.py
+++ /dev/null
diff --git a/tuskar_ui/api/flavor.py b/tuskar_ui/api/flavor.py
deleted file mode 100644
index 518400cc..00000000
--- a/tuskar_ui/api/flavor.py
+++ /dev/null
@@ -1,121 +0,0 @@
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
-# not use this file except in compliance with the License. You may obtain
-# a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
-
-import logging
-
-from django.utils.translation import ugettext_lazy as _
-from horizon.utils import memoized
-from openstack_dashboard.api import nova
-
-import tuskar_ui
-from tuskar_ui.cached_property import cached_property # noqa
-from tuskar_ui.handle_errors import handle_errors # noqa
-
-
-LOG = logging.getLogger(__name__)
-
-
-class Flavor(object):
-
- def __init__(self, flavor):
- """Construct by wrapping Nova flavor
-
- :param flavor: Nova flavor
- :type flavor: novaclient.v2.flavors.Flavor
- """
- self._flavor = flavor
-
- def __getattr__(self, name):
- return getattr(self._flavor, name)
-
- @property
- def ram_bytes(self):
- """Get RAM size in bytes
-
- Default RAM size is in MB.
- """
- return self.ram * 1024 * 1024
-
- @property
- def disk_bytes(self):
- """Get disk size in bytes
-
- Default disk size is in GB.
- """
- return self.disk * 1024 * 1024 * 1024
-
- @cached_property
- def extras_dict(self):
- """Return extra flavor parameters
-
- :return: Nova flavor keys
- :rtype: dict
- """
- return self._flavor.get_keys()
-
- @property
- def cpu_arch(self):
- return self.extras_dict.get('cpu_arch', '')
-
- @property
- def kernel_image_id(self):
- return self.extras_dict.get('baremetal:deploy_kernel_id', '')
-
- @property
- def ramdisk_image_id(self):
- return self.extras_dict.get('baremetal:deploy_ramdisk_id', '')
-
- @classmethod
- def create(cls, request, name, memory, vcpus, disk, cpu_arch,
- kernel_image_id=None, ramdisk_image_id=None):
- extras_dict = {
- 'cpu_arch': cpu_arch,
- 'capabilities:boot_option': 'local',
- }
- if kernel_image_id is not None:
- extras_dict['baremetal:deploy_kernel_id'] = kernel_image_id
- if ramdisk_image_id is not None:
- extras_dict['baremetal:deploy_ramdisk_id'] = ramdisk_image_id
- return cls(nova.flavor_create(request, name, memory, vcpus, disk,
- metadata=extras_dict))
-
- @classmethod
- @handle_errors(_("Unable to load flavor."))
- def get(cls, request, flavor_id):
- return cls(nova.flavor_get(request, flavor_id))
-
- @classmethod
- @handle_errors(_("Unable to load flavor."))
- def get_by_name(cls, request, name):
- for flavor in cls.list(request):
- if flavor.name == name:
- return flavor
-
- @classmethod
- @handle_errors(_("Unable to retrieve flavor list."), [])
- def list(cls, request):
- return [cls(item) for item in nova.flavor_list(request)]
-
- @classmethod
- @memoized.memoized
- @handle_errors(_("Unable to retrieve existing servers list."), [])
- def list_deployed_ids(cls, request):
- """Get and memoize ID's of deployed flavors."""
- servers = nova.server_list(request)[0]
- deployed_ids = set(server.flavor['id'] for server in servers)
- deployed_names = []
- for plan in tuskar_ui.api.tuskar.Plan.list(request):
- deployed_names.extend(
- [plan.parameter_value(role.flavor_parameter_name)
- for role in plan.role_list])
- return [flavor.id for flavor in cls.list(request)
- if flavor.id in deployed_ids or flavor.name in deployed_names]
diff --git a/tuskar_ui/api/heat.py b/tuskar_ui/api/heat.py
deleted file mode 100644
index 18056de5..00000000
--- a/tuskar_ui/api/heat.py
+++ /dev/null
@@ -1,553 +0,0 @@
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
-# not use this file except in compliance with the License. You may obtain
-# a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
-
-import logging
-import os
-import tempfile
-import urlparse
-
-from django.conf import settings
-from django.utils.translation import ugettext_lazy as _
-from heatclient.common import template_utils
-from heatclient.exc import HTTPNotFound
-from horizon.utils import memoized
-from openstack_dashboard.api import base
-from openstack_dashboard.api import heat
-from openstack_dashboard.api import keystone
-
-from tuskar_ui.api import node
-from tuskar_ui.api import tuskar
-from tuskar_ui.cached_property import cached_property # noqa
-from tuskar_ui.handle_errors import handle_errors # noqa
-from tuskar_ui.utils import utils
-
-
-LOG = logging.getLogger(__name__)
-
-
-@memoized.memoized
-def overcloud_keystoneclient(request, endpoint, password):
- """Returns a client connected to the Keystone backend.
-
- Several forms of authentication are supported:
-
- * Username + password -> Unscoped authentication
- * Username + password + tenant id -> Scoped authentication
- * Unscoped token -> Unscoped authentication
- * Unscoped token + tenant id -> Scoped authentication
- * Scoped token -> Scoped authentication
-
- Available services and data from the backend will vary depending on
- whether the authentication was scoped or unscoped.
-
- Lazy authentication if an ``endpoint`` parameter is provided.
-
- Calls requiring the admin endpoint should have ``admin=True`` passed in
- as a keyword argument.
-
- The client is cached so that subsequent API calls during the same
- request/response cycle don't have to be re-authenticated.
- """
- api_version = keystone.VERSIONS.get_active_version()
-
- # TODO(lsmola) add support of certificates and secured http and rest of
- # parameters according to horizon and add configuration to local settings
- # (somehow plugin based, we should not maintain a copy of settings)
- LOG.debug("Creating a new keystoneclient connection to %s." % endpoint)
-
- # TODO(lsmola) we should create tripleo-admin user for this purpose
- # this needs to be done first on tripleo side
- conn = api_version['client'].Client(username="admin",
- password=password,
- tenant_name="admin",
- auth_url=endpoint)
-
- return conn
-
-
-def _save_templates(templates):
- """Saves templates into tmpdir on server
-
- This should go away and get replaced by libutils.save_templates from
- tripleo-common https://github.com/openstack/tripleo-common/
- """
- output_dir = tempfile.mkdtemp()
-
- for template_name, template_content in templates.items():
-
- # It's possible to organize the role templates and their dependent
- # files into directories, in which case the template_name will carry
- # the directory information. If that's the case, first create the
- # directory structure (if it hasn't already been created by another
- # file in the templates list).
- template_dir = os.path.dirname(template_name)
- output_template_dir = os.path.join(output_dir, template_dir)
- if template_dir and not os.path.exists(output_template_dir):
- os.makedirs(output_template_dir)
-
- filename = os.path.join(output_dir, template_name)
- with open(filename, 'w+') as template_file:
- template_file.write(template_content)
- return output_dir
-
-
-def _process_templates(templates):
- """Process templates
-
- Due to bug in heat api
- https://bugzilla.redhat.com/show_bug.cgi?id=1212740, we need to
- save the templates in tmpdir, reprocess them with template_utils
- from heatclient and then we can use them in creating/updating stack.
-
- This should be replaced by the same code that is in tripleo-common and
- eventually it will not be needed at all.
- """
-
- tpl_dir = _save_templates(templates)
-
- tpl_files, template = template_utils.get_template_contents(
- template_file=os.path.join(tpl_dir, tuskar.MASTER_TEMPLATE_NAME))
- env_files, env = (
- template_utils.process_multiple_environments_and_files(
- env_paths=[os.path.join(tpl_dir, tuskar.ENVIRONMENT_NAME)]))
-
- files = dict(list(tpl_files.items()) + list(env_files.items()))
-
- return template, env, files
-
-
-class Stack(base.APIResourceWrapper):
- _attrs = ('id', 'stack_name', 'outputs', 'stack_status', 'parameters')
-
- def __init__(self, apiresource, request=None):
- super(Stack, self).__init__(apiresource)
- self._request = request
-
- @classmethod
- def create(cls, request, stack_name, templates):
- template, environment, files = _process_templates(templates)
-
- fields = {
- 'stack_name': stack_name,
- 'template': template,
- 'environment': environment,
- 'files': files,
- 'timeout_mins': 240,
- }
- password = getattr(settings, 'UNDERCLOUD_ADMIN_PASSWORD', None)
- stack = heat.stack_create(request, password, **fields)
- return cls(stack, request=request)
-
- def update(self, request, stack_name, templates):
- template, environment, files = _process_templates(templates)
-
- fields = {
- 'stack_name': stack_name,
- 'template': template,
- 'environment': environment,
- 'files': files,
- }
- password = getattr(settings, 'UNDERCLOUD_ADMIN_PASSWORD', None)
- heat.stack_update(request, self.id, password, **fields)
-
- @classmethod
- @handle_errors(_("Unable to retrieve heat stacks"), [])
- def list(cls, request):
- """Return a list of stacks in Heat
-
- :param request: request object
- :type request: django.http.HttpRequest
-
- :return: list of Heat stacks, or an empty list if there
- are none
- :rtype: list of tuskar_ui.api.heat.Stack
- """
- stacks, has_more_data, has_prev_data = heat.stacks_list(request)
- return [cls(stack, request=request) for stack in stacks]
-
- @classmethod
- @handle_errors(_("Unable to retrieve stack"))
- def get(cls, request, stack_id):
- """Return the Heat Stack associated with this Overcloud
-
- :return: Heat Stack associated with the stack_id; or None
- if no Stack is associated, or no Stack can be
- found
- :rtype: tuskar_ui.api.heat.Stack or None
- """
- return cls(heat.stack_get(request, stack_id), request=request)
-
- @classmethod
- @handle_errors(_("Unable to retrieve stack"))
- def get_by_plan(cls, request, plan):
- """Return the Heat Stack associated with a Plan
-
- :return: Heat Stack associated with the plan; or None
- if no Stack is associated, or no Stack can be
- found
- :rtype: tuskar_ui.api.heat.Stack or None
- """
- # TODO(lsmola) until we have working deployment through Tuskar-API,
- # this will not work
- # for stack in Stack.list(request):
- # if stack.plan and (stack.plan.id == plan.id):
- # return stack
- try:
- stack = Stack.list(request)[0]
- except IndexError:
- return None
- # TODO(lsmola) stack list actually does not contain all the detail
- # info, there should be call for that, investigate
- return Stack.get(request, stack.id)
-
- @classmethod
- @handle_errors(_("Unable to delete Heat stack"), [])
- def delete(cls, request, stack_id):
- heat.stack_delete(request, stack_id)
-
- @memoized.memoized
- def resources(self, with_joins=True, role=None):
- """Return list of OS::Nova::Server Resources
-
- Return list of OS::Nova::Server Resources associated with the Stack
- and which are associated with a Role
-
- :param with_joins: should we also retrieve objects associated with each
- retrieved Resource?
- :type with_joins: bool
-
- :return: list of all Resources or an empty list if there are none
- :rtype: list of tuskar_ui.api.heat.Resource
- """
-
- if role:
- roles = [role]
- else:
- roles = self.plan.role_list
- resource_dicts = []
-
- # A provider resource is deployed as a nested stack, so we have to
- # drill down and retrieve those that match a tuskar role
- for role in roles:
- resource_group_name = role.name
- try:
- resource_group = heat.resource_get(self._request,
- self.id,
- resource_group_name)
-
- group_resources = heat.resources_list(
- self._request, resource_group.physical_resource_id)
- for group_resource in group_resources:
- if not group_resource.physical_resource_id:
- # Skip groups who has no physical resource.
- continue
- nova_resources = heat.resources_list(
- self._request,
- group_resource.physical_resource_id)
- resource_dicts.extend([{"resource": resource,
- "role": role}
- for resource in nova_resources])
-
- except HTTPNotFound:
- pass
-
- if not with_joins:
- return [Resource(rd['resource'], request=self._request,
- stack=self, role=rd['role'])
- for rd in resource_dicts]
-
- nodes_dict = utils.list_to_dict(node.Node.list(self._request,
- associated=True),
- key_attribute='instance_uuid')
- joined_resources = []
- for rd in resource_dicts:
- resource = rd['resource']
- joined_resources.append(
- Resource(resource,
- node=nodes_dict.get(resource.physical_resource_id,
- None),
- request=self._request, stack=self, role=rd['role']))
- # TODO(lsmola) I want just resources with nova instance
- # this could be probably filtered a better way, investigate
- return [r for r in joined_resources if r.node is not None]
-
- @memoized.memoized
- def resources_count(self, overcloud_role=None):
- """Return count of associated Resources
-
- :param overcloud_role: role of resources to be counted; None means all
- :type overcloud_role: tuskar_ui.api.tuskar.Role
-
- :return: Number of matching resources
- :rtype: int
- """
- # TODO(dtantsur): there should be better way to do it, rather than
- # fetching and calling len()
- # FIXME(dtantsur): should also be able to use with_joins=False
- # but unable due to bug #1289505
- if overcloud_role is None:
- resources = self.resources()
- else:
- resources = self.resources(role=overcloud_role)
- return len(resources)
-
- @cached_property
- def plan(self):
- """return associated Plan if a plan_id exists within stack parameters.
-
- :return: associated Plan if plan_id exists and a matching plan
- exists as well; None otherwise
- :rtype: tuskar_ui.api.tuskar.Plan
- """
- # TODO(lsmola) replace this by actual reference, I am pretty sure
- # the relation won't be stored in parameters, that would mean putting
- # that into template, which doesn't make sense
- # if 'plan_id' in self.parameters:
- # return tuskar.Plan.get(self._request,
- # self.parameters['plan_id'])
- try:
- plan = tuskar.Plan.list(self._request)[0]
- except IndexError:
- return None
- return plan
-
- @cached_property
- def is_initialized(self):
- """Check if this Stack is successfully initialized.
-
- :return: True if this Stack is successfully initialized, False
- otherwise
- :rtype: bool
- """
- return len(self.dashboard_urls) > 0
-
- @cached_property
- def is_deployed(self):
- """Check if this Stack is successfully deployed.
-
- :return: True if this Stack is successfully deployed, False otherwise
- :rtype: bool
- """
- return self.stack_status in ('CREATE_COMPLETE',
- 'UPDATE_COMPLETE')
-
- @cached_property
- def is_deploying(self):
- """Check if this Stack is currently deploying.
-
- :return: True if deployment is in progress, False otherwise.
- :rtype: bool
- """
- return self.stack_status in ('CREATE_IN_PROGRESS',)
-
- @cached_property
- def is_updating(self):
- """Check if this Stack is currently updating.
-
- :return: True if updating is in progress, False otherwise.
- :rtype: bool
- """
- return self.stack_status in ('UPDATE_IN_PROGRESS',)
-
- @cached_property
- def is_failed(self):
- """Check if this Stack failed to update or deploy.
-
- :return: True if deployment there was an error, False otherwise.
- :rtype: bool
- """
- return self.stack_status in ('CREATE_FAILED',
- 'UPDATE_FAILED',)
-
- @cached_property
- def is_deleting(self):
- """Check if this Stack is deleting.
-
- :return: True if Stack is deleting, False otherwise.
- :rtype: bool
- """
- return self.stack_status in ('DELETE_IN_PROGRESS', )
-
- @cached_property
- def is_delete_failed(self):
- """Check if Stack deleting has failed.
-
- :return: True if Stack deleting has failed, False otherwise.
- :rtype: bool
- """
- return self.stack_status in ('DELETE_FAILED', )
-
- @cached_property
- def events(self):
- """Return the Heat Events associated with this Stack
-
- :return: list of Heat Events associated with this Stack;
- or an empty list if there is no Stack associated with
- this Stack, or there are no Events
- :rtype: list of heatclient.v1.events.Event
- """
- return heat.events_list(self._request,
- self.stack_name)
-
- @property
- def stack_outputs(self):
- return getattr(self, 'outputs', [])
-
- @cached_property
- def keystone_auth_url(self):
- for output in self.stack_outputs:
- if output['output_key'] == 'KeystoneURL':
- return output['output_value']
-
- @cached_property
- def keystone_ip(self):
- if self.keystone_auth_url:
- return urlparse.urlparse(self.keystone_auth_url).hostname
-
- @cached_property
- def overcloud_keystone(self):
- try:
- return overcloud_keystoneclient(
- self._request,
- self.keystone_auth_url,
- self.plan.parameter_value('Controller-1::AdminPassword'))
- except Exception:
- LOG.debug('Unable to connect to overcloud keystone.')
- return None
-
- @cached_property
- def dashboard_urls(self):
- client = self.overcloud_keystone
- if not client:
- return []
-
- try:
- services = client.services.list()
- for service in services:
- if service.name == 'horizon':
- break
- else:
- return []
- except Exception:
- return []
-
- admin_urls = [endpoint.adminurl for endpoint
- in client.endpoints.list()
- if endpoint.service_id == service.id]
-
- return admin_urls
-
-
-class Resource(base.APIResourceWrapper):
- _attrs = ('resource_name', 'resource_type', 'resource_status',
- 'physical_resource_id')
-
- def __init__(self, apiresource, request=None, **kwargs):
- """Initialize a resource
-
- :param apiresource: apiresource we want to wrap
- :type apiresource: heatclient.v1.resources.Resource
-
- :param request: request
- :type request: django.core.handlers.wsgi.WSGIRequest
-
- :param node: node relation we want to cache
- :type node: tuskar_ui.api.node.Node
-
- :return: Resource object
- :rtype: Resource
- """
- super(Resource, self).__init__(apiresource)
- self._request = request
- if 'node' in kwargs:
- self._node = kwargs['node']
- if 'stack' in kwargs:
- self._stack = kwargs['stack']
- if 'role' in kwargs:
- self._role = kwargs['role']
-
- @classmethod
- @memoized.memoized
- def _resources_by_nodes(cls, request):
- return {resource.physical_resource_id: resource
- for resource in cls.list_all_resources(request)}
-
- @classmethod
- def get_by_node(cls, request, node):
- """Return the specified Heat Resource given a Node
-
- :param request: request object
- :type request: django.http.HttpRequest
-
- :param node: node to match
- :type node: tuskar_ui.api.node.Node
-
- :return: matching Resource, or raises LookupError if no
- resource matches the node
- :rtype: tuskar_ui.api.heat.Resource
- """
- return cls._resources_by_nodes(request)[node.instance_uuid]
-
- @classmethod
- def list_all_resources(cls, request):
- """Iterate through all the stacks and return all relevant resources
-
- :param request: request object
- :type request: django.http.HttpRequest
-
- :return: list of resources
- :rtype: list of tuskar_ui.api.heat.Resource
- """
- all_resources = []
- for stack in Stack.list(request):
- all_resources.extend(stack.resources(with_joins=False))
- return all_resources
-
- @cached_property
- def role(self):
- """Return the Role associated with this Resource
-
- :return: Role associated with this Resource, or None if no
- Role is associated
- :rtype: tuskar_ui.api.tuskar.Role
- """
- if hasattr(self, '_role'):
- return self._role
-
- @cached_property
- def node(self):
- """Return the Ironic Node associated with this Resource
-
- :return: Ironic Node associated with this Resource, or None if no
- Node is associated
- :rtype: tuskar_ui.api.node.Node
-
- :raises: ironicclient.exc.HTTPNotFound if there is no Node with the
- matching instance UUID
- """
- if hasattr(self, '_node'):
- return self._node
- if self.physical_resource_id:
- return node.Node.get_by_instance_uuid(self._request,
- self.physical_resource_id)
- return None
-
- @cached_property
- def stack(self):
- """Return the Stack associated with this Resource
-
- :return: Stack associated with this Resource, or None if no
- Stack is associated
- :rtype: tuskar_ui.api.heat.Stack
- """
- if hasattr(self, '_stack'):
- return self._stack
diff --git a/tuskar_ui/api/node.py b/tuskar_ui/api/node.py
deleted file mode 100644
index e9fef71b..00000000
--- a/tuskar_ui/api/node.py
+++ /dev/null
@@ -1,445 +0,0 @@
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
-# not use this file except in compliance with the License. You may obtain
-# a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
-
-import logging
-import time
-
-from django.conf import settings
-from django.utils.translation import ugettext_lazy as _
-from horizon.utils import memoized
-from ironic_inspector_client import client as inspector_client
-from ironicclient import client as ironic_client
-from openstack_dashboard.api import base
-from openstack_dashboard.api import glance
-from openstack_dashboard.api import nova
-
-from tuskar_ui.cached_property import cached_property # noqa
-from tuskar_ui.handle_errors import handle_errors # noqa
-from tuskar_ui.utils import utils
-
-
-# power states
-ERROR_STATES = set(['deploy failed', 'error'])
-POWER_ON_STATES = set(['on', 'power on'])
-
-# provision_states of ironic aggregated to reasonable groups
-PROVISION_STATE_FREE = ['available', 'deleted', None]
-PROVISION_STATE_PROVISIONED = ['active']
-PROVISION_STATE_PROVISIONING = [
- 'deploying', 'wait call-back', 'rebuild', 'deploy complete']
-PROVISION_STATE_DELETING = ['deleting']
-PROVISION_STATE_ERROR = ['error', 'deploy failed']
-
-# names for states of ironic used in UI,
-# provison_states + discovery states
-DISCOVERING_STATE = 'discovering'
-DISCOVERED_STATE = 'discovered'
-DISCOVERY_FAILED_STATE = 'discovery failed'
-MAINTENANCE_STATE = 'manageable'
-PROVISIONED_STATE = 'provisioned'
-PROVISIONING_FAILED_STATE = 'provisioning failed'
-PROVISIONING_STATE = 'provisioning'
-DELETING_STATE = 'deleting'
-FREE_STATE = 'free'
-
-
-IRONIC_DISCOVERD_URL = getattr(settings, 'IRONIC_DISCOVERD_URL', None)
-LOG = logging.getLogger(__name__)
-
-
-@memoized.memoized
-def ironicclient(request):
- api_version = 1
- kwargs = {'os_auth_token': request.user.token.id,
- 'ironic_url': base.url_for(request, 'baremetal')}
- return ironic_client.get_client(api_version, **kwargs)
-
-
-# FIXME(lsmola) This should be done in Horizon, they don't have caching
-@memoized.memoized
-@handle_errors(_("Unable to retrieve image."))
-def image_get(request, image_id):
- """Returns an Image object with metadata
-
- Returns an Image object populated with metadata for image
- with supplied identifier.
-
- :param image_id: list of objects to be put into a dict
- :type image_id: list
-
- :return: object
- :rtype: glanceclient.v1.images.Image
- """
- image = glance.image_get(request, image_id)
- return image
-
-
-class Node(base.APIResourceWrapper):
- _attrs = ('id', 'uuid', 'instance_uuid', 'driver', 'driver_info',
- 'properties', 'power_state', 'target_power_state',
- 'provision_state', 'maintenance', 'extra')
-
- def __init__(self, apiresource, request=None, instance=None):
- """Initialize a Node
-
- :param apiresource: apiresource we want to wrap
- :type apiresource: IronicNode
-
- :param request: request
- :type request: django.core.handlers.wsgi.WSGIRequest
-
- :param instance: instance relation we want to cache
- :type instance: openstack_dashboard.api.nova.Server
-
- :return: Node object
- :rtype: tusar_ui.api.node.Node
- """
- super(Node, self).__init__(apiresource)
- self._request = request
- self._instance = instance
-
- @classmethod
- def create(cls, request, ipmi_address=None, cpu_arch=None, cpus=None,
- memory_mb=None, local_gb=None, mac_addresses=[],
- ipmi_username=None, ipmi_password=None, ssh_address=None,
- ssh_username=None, ssh_key_contents=None,
- deployment_kernel=None, deployment_ramdisk=None,
- driver=None):
- """Create a Node in Ironic."""
- if driver == 'pxe_ssh':
- driver_info = {
- 'ssh_address': ssh_address,
- 'ssh_username': ssh_username,
- 'ssh_key_contents': ssh_key_contents,
- 'ssh_virt_type': 'virsh',
- }
- else:
- driver_info = {
- 'ipmi_address': ipmi_address,
- 'ipmi_username': ipmi_username,
- 'ipmi_password': ipmi_password
- }
- driver_info.update(
- deploy_kernel=deployment_kernel,
- deploy_ramdisk=deployment_ramdisk
- )
-
- properties = {'capabilities': 'boot_option:local', }
- if cpus:
- properties.update(cpus=cpus)
- if memory_mb:
- properties.update(memory_mb=memory_mb)
- if local_gb:
- properties.update(local_gb=local_gb)
- if cpu_arch:
- properties.update(cpu_arch=cpu_arch)
-
- node = ironicclient(request).node.create(
- driver=driver,
- driver_info=driver_info,
- properties=properties,
- )
- for mac_address in mac_addresses:
- ironicclient(request).port.create(
- node_uuid=node.uuid,
- address=mac_address
- )
-
- return cls(node, request)
-
- @classmethod
- @memoized.memoized
- @handle_errors(_("Unable to retrieve node"))
- def get(cls, request, uuid):
- """Return the Node that matches the ID
-
- :param request: request object
- :type request: django.http.HttpRequest
-
- :param uuid: ID of Node to be retrieved
- :type uuid: str
-
- :return: matching Node, or None if no IronicNode matches the ID
- :rtype: tuskar_ui.api.node.Node
- """
- node = ironicclient(request).node.get(uuid)
- if node.instance_uuid is not None:
- server = nova.server_get(request, node.instance_uuid)
- else:
- server = None
- return cls(node, request, server)
-
- @classmethod
- @handle_errors(_("Unable to retrieve node"))
- def get_by_instance_uuid(cls, request, instance_uuid):
- """Return the Node associated with the instance ID
-
- :param request: request object
- :type request: django.http.HttpRequest
-
- :param instance_uuid: ID of Instance that is deployed on the Node
- to be retrieved
- :type instance_uuid: str
-
- :return: matching Node
- :rtype: tuskar_ui.api.node.Node
-
- :raises: ironicclient.exc.HTTPNotFound if there is no Node with
- the matching instance UUID
- """
- node = ironicclient(request).node.get_by_instance_uuid(instance_uuid)
- server = nova.server_get(request, instance_uuid)
- return cls(node, request, server)
-
- @classmethod
- @memoized.memoized
- @handle_errors(_("Unable to retrieve nodes"), [])
- def list(cls, request, associated=None, maintenance=None):
- """Return a list of Nodes
-
- :param request: request object
- :type request: django.http.HttpRequest
-
- :param associated: should we also retrieve all Nodes, only those
- associated with an Instance, or only those not
- associated with an Instance?
- :type associated: bool
-
- :param maintenance: should we also retrieve all Nodes, only those
- in maintenance mode, or those which are not in
- maintenance mode?
- :type maintenance: bool
-
- :return: list of Nodes, or an empty list if there are none
- :rtype: list of tuskar_ui.api.node.Node
- """
- nodes = ironicclient(request).node.list(associated=associated,
- maintenance=maintenance)
- if associated is None or associated:
- servers = nova.server_list(request)[0]
- servers_dict = utils.list_to_dict(servers)
- nodes_with_instance = []
- for n in nodes:
- server = servers_dict.get(n.instance_uuid, None)
- nodes_with_instance.append(cls(n, instance=server,
- request=request))
- return [cls.get(request, node.uuid)
- for node in nodes_with_instance]
- return [cls.get(request, node.uuid) for node in nodes]
-
- @classmethod
- def delete(cls, request, uuid):
- """Delete an Node
-
- Remove the IronicNode matching the ID if it
- exists; otherwise, does nothing.
-
- :param request: request object
- :type request: django.http.HttpRequest
-
- :param uuid: ID of IronicNode to be removed
- :type uuid: str
- """
- return ironicclient(request).node.delete(uuid)
-
- @classmethod
- def discover(cls, request, uuids):
- """Set the maintenance status of node
-
- :param request: request object
- :type request: django.http.HttpRequest
-
- :param uuids: IDs of IronicNodes
- :type uuids: list of str
- """
- if not IRONIC_DISCOVERD_URL:
- return
- for uuid in uuids:
-
- inspector_client.introspect(
- uuid,
- base_url=IRONIC_DISCOVERD_URL,
- auth_token=request.user.token.id)
-
- # NOTE(dtantsur): PXE firmware on virtual machines misbehaves when
- # a lot of nodes start DHCPing simultaneously: it ignores NACK from
- # DHCP server, tries to get the same address, then times out. Work
- # around it by using sleep, anyway introspection takes much longer.
- time.sleep(5)
-
- @classmethod
- def set_maintenance(cls, request, uuid, maintenance):
- """Set the maintenance status of node
-
- :param request: request object
- :type request: django.http.HttpRequest
-
- :param uuid: ID of Node to be removed
- :type uuid: str
-
- :param maintenance: desired maintenance state
- :type maintenance: bool
- """
- patch = {
- 'op': 'replace',
- 'value': 'True' if maintenance else 'False',
- 'path': '/maintenance'
- }
- node = ironicclient(request).node.update(uuid, [patch])
- return cls(node, request)
-
- @classmethod
- def set_power_state(cls, request, uuid, power_state):
- """Set the power_state of node
-
- :param request: request object
- :type request: django.http.HttpRequest
-
- :param uuid: ID of Node
- :type uuid: str
-
- :param power_state: desired power_state
- :type power_state: str
- """
- node = ironicclient(request).node.set_power_state(uuid, power_state)
- return cls(node, request)
-
- @classmethod
- @memoized.memoized
- def list_ports(cls, request, uuid):
- """Return a list of ports associated with this Node
-
- :param request: request object
- :type request: django.http.HttpRequest
-
- :param uuid: ID of IronicNode
- :type uuid: str
- """
- return ironicclient(request).node.list_ports(uuid)
-
- @cached_property
- def addresses(self):
- """Return a list of port addresses associated with this IronicNode
-
- :return: list of port addresses associated with this IronicNode, or
- an empty list if no addresses are associated with
- this IronicNode
- :rtype: list of str
- """
- ports = self.list_ports(self._request, self.uuid)
- return [port.address for port in ports]
-
- @cached_property
- def cpus(self):
- return self.properties.get('cpus', None)
-
- @cached_property
- def memory_mb(self):
- return self.properties.get('memory_mb', None)
-
- @cached_property
- def local_gb(self):
- return self.properties.get('local_gb', None)
-
- @cached_property
- def cpu_arch(self):
- return self.properties.get('cpu_arch', None)
-
- @cached_property
- def state(self):
- if self.maintenance:
- if not IRONIC_DISCOVERD_URL:
- return MAINTENANCE_STATE
- try:
- status = inspector_client.get_status(
- uuid=self.uuid,
- base_url=IRONIC_DISCOVERD_URL,
- auth_token=self._request.user.token.id,
- )
- except inspector_client.ClientError as e:
- if getattr(e.response, 'status_code', None) == 404:
- return MAINTENANCE_STATE
- raise
- if status['error']:
- return DISCOVERY_FAILED_STATE
- elif status['finished']:
- return DISCOVERED_STATE
- else:
- return DISCOVERING_STATE
- else:
- if self.provision_state in PROVISION_STATE_FREE:
- return FREE_STATE
- if self.provision_state in PROVISION_STATE_PROVISIONING:
- return PROVISIONING_STATE
- if self.provision_state in PROVISION_STATE_PROVISIONED:
- return PROVISIONED_STATE
- if self.provision_state in PROVISION_STATE_DELETING:
- return DELETING_STATE
- if self.provision_state in PROVISION_STATE_ERROR:
- return PROVISIONING_FAILED_STATE
- # Unknown state
- return None
-
- @cached_property
- def instance(self):
- """Return the Nova Instance associated with this Node
-
- :return: Nova Instance associated with this Node; or
- None if there is no Instance associated with this
- Node, or no matching Instance is found
- :rtype: Instance
- """
- if self._instance is not None:
- return self._instance
- if self.instance_uuid:
- servers, _has_more_data = nova.server_list(self._request)
- for server in servers:
- if server.id == self.instance_uuid:
- return server
-
- @cached_property
- def ip_address(self):
- try:
- apiresource = self.instace._apiresource
- except AttributeError:
- LOG.error("Couldn't obtain IP address")
- return None
- return apiresource.addresses['ctlplane'][0]['addr']
-
- @cached_property
- def image_name(self):
- """Return image name of associated instance
-
- Returns image name of instance associated with node
-
- :return: Image name of instance
- :rtype: string
- """
- if self.instance is None:
- return
- image = image_get(self._request, self.instance.image['id'])
- return image.name
-
- @cached_property
- def instance_status(self):
- return getattr(getattr(self, 'instance', None), 'status', None)
-
- @cached_property
- def provisioning_status(self):
- if self.instance_uuid:
- return _("Provisioned")
- return _("Free")
-
- @classmethod
- def get_all_mac_addresses(cls, request):
- macs = [node.addresses for node in cls.list(request)]
- return set([mac.upper() for sublist in macs for mac in sublist])
diff --git a/tuskar_ui/api/tuskar.py b/tuskar_ui/api/tuskar.py
deleted file mode 100644
index 89244591..00000000
--- a/tuskar_ui/api/tuskar.py
+++ /dev/null
@@ -1,558 +0,0 @@
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
-# not use this file except in compliance with the License. You may obtain
-# a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
-
-import logging
-import random
-import string
-
-from django.conf import settings
-from django.utils.translation import ugettext_lazy as _
-from glanceclient import exc as glance_exceptions
-from horizon.utils import memoized
-from openstack_dashboard.api import base
-from openstack_dashboard.api import glance
-from openstack_dashboard.api import neutron
-from os_cloud_config import keystone_pki
-from tuskarclient import client as tuskar_client
-
-from tuskar_ui.api import flavor
-from tuskar_ui.cached_property import cached_property # noqa
-from tuskar_ui.handle_errors import handle_errors # noqa
-
-LOG = logging.getLogger(__name__)
-MASTER_TEMPLATE_NAME = 'plan.yaml'
-ENVIRONMENT_NAME = 'environment.yaml'
-TUSKAR_SERVICE = 'management'
-
-SSL_HIDDEN_PARAMS = ('SSLCertificate', 'SSLKey')
-KEYSTONE_CERTIFICATE_PARAMS = (
- 'KeystoneSigningCertificate', 'KeystoneCACertificate',
- 'KeystoneSigningKey')
-
-
-@memoized.memoized
-def tuskarclient(request, password=None):
- api_version = "2"
- insecure = getattr(settings, 'OPENSTACK_SSL_NO_VERIFY', False)
- ca_file = getattr(settings, 'OPENSTACK_SSL_CACERT', None)
- endpoint = base.url_for(request, TUSKAR_SERVICE)
-
- LOG.debug('tuskarclient connection created using token "%s" and url "%s"' %
- (request.user.token.id, endpoint))
-
- client = tuskar_client.get_client(api_version,
- tuskar_url=endpoint,
- insecure=insecure,
- ca_file=ca_file,
- username=request.user.username,
- password=password,
- os_auth_token=request.user.token.id)
- return client
-
-
-def password_generator(size=40, chars=(string.ascii_uppercase +
- string.ascii_lowercase +
- string.digits)):
- return ''.join(random.choice(chars) for _ in range(size))
-
-
-def strip_prefix(parameter_name):
- return parameter_name.split('::', 1)[-1]
-
-
-def _is_blank(parameter):
- return not parameter['value'] or parameter['value'] == 'unset'
-
-
-def _should_generate_password(parameter):
- # TODO(lsmola) Filter out SSL params for now. Once it will be generated
- # in TripleO add it here too. Note: this will also affect how endpoints are
- # created
- key = parameter['name']
- return all([
- parameter['hidden'],
- _is_blank(parameter),
- strip_prefix(key) not in SSL_HIDDEN_PARAMS,
- strip_prefix(key) not in KEYSTONE_CERTIFICATE_PARAMS,
- key != 'SnmpdReadonlyUserPassword',
- ])
-
-
-def _should_generate_keystone_cert(parameter):
- return all([
- strip_prefix(parameter['name']) in KEYSTONE_CERTIFICATE_PARAMS,
- _is_blank(parameter),
- ])
-
-
-def _should_generate_neutron_control_plane(parameter):
- return all([
- strip_prefix(parameter['name']) == 'NeutronControlPlaneID',
- _is_blank(parameter),
- ])
-
-
-class Plan(base.APIResourceWrapper):
- _attrs = ('uuid', 'name', 'description', 'created_at', 'modified_at',
- 'roles', 'parameters')
-
- def __init__(self, apiresource, request=None):
- super(Plan, self).__init__(apiresource)
- self._request = request
-
- @classmethod
- def create(cls, request, name, description):
- """Create a Plan in Tuskar
-
- :param request: request object
- :type request: django.http.HttpRequest
-
- :param name: plan name
- :type name: string
-
- :param description: plan description
- :type description: string
-
- :return: the created Plan object
- :rtype: tuskar_ui.api.tuskar.Plan
- """
- plan = tuskarclient(request).plans.create(name=name,
- description=description)
- return cls(plan, request=request)
-
- @classmethod
- def patch(cls, request, plan_id, parameters):
- """Update a Plan in Tuskar
-
- :param request: request object
- :type request: django.http.HttpRequest
-
- :param plan_id: id of the plan we want to update
- :type plan_id: string
-
- :param parameters: new values for the plan's parameters
- :type parameters: dict
-
- :return: the updated Plan object
- :rtype: tuskar_ui.api.tuskar.Plan
- """
- parameter_list = [{
- 'name': unicode(name),
- 'value': unicode(value),
- } for (name, value) in parameters.items()]
- plan = tuskarclient(request).plans.patch(plan_id, parameter_list)
- return cls(plan, request=request)
-
- @classmethod
- @memoized.memoized
- def list(cls, request):
- """Return a list of Plans in Tuskar
-
- :param request: request object
- :type request: django.http.HttpRequest
-
- :return: list of Plans, or an empty list if there are none
- :rtype: list of tuskar_ui.api.tuskar.Plan
- """
- plans = tuskarclient(request).plans.list()
- return [cls(plan, request=request) for plan in plans]
-
- @classmethod
- @handle_errors(_("Unable to retrieve plan"))
- def get(cls, request, plan_id):
- """Return the Plan that matches the ID
-
- :param request: request object
- :type request: django.http.HttpRequest
-
- :param plan_id: id of Plan to be retrieved
- :type plan_id: int
-
- :return: matching Plan, or None if no Plan matches
- the ID
- :rtype: tuskar_ui.api.tuskar.Plan
- """
- plan = tuskarclient(request).plans.get(plan_uuid=plan_id)
- return cls(plan, request=request)
-
- # TODO(lsmola) before will will support multiple overclouds, we
- # can work only with overcloud that is named overcloud. Delete
- # this once we have more overclouds. Till then, this is the overcloud
- # that rules them all.
- # This is how API supports it now, so we have to have it this way.
- # Also till Overcloud workflow is done properly, we have to work
- # with situations that overcloud is deleted, but stack is still
- # there. So overcloud will pretend to exist when stack exist.
- @classmethod
- def get_the_plan(cls, request):
- plan_list = cls.list(request)
- for plan in plan_list:
- return plan
- # if plan doesn't exist, create it
- plan = cls.create(request, 'overcloud', 'overcloud')
- return plan
-
- @classmethod
- def delete(cls, request, plan_id):
- """Delete a Plan
-
- :param request: request object
- :type request: django.http.HttpRequest
-
- :param plan_id: plan id
- :type plan_id: int
- """
- tuskarclient(request).plans.delete(plan_uuid=plan_id)
-
- @cached_property
- def role_list(self):
- return [Role.get(self._request, role.uuid)
- for role in self.roles]
-
- @cached_property
- def _roles_by_name(self):
- return dict((role.name, role) for role in self.role_list)
-
- def get_role_by_name(self, role_name):
- """Get the role with the given name."""
- return self._roles_by_name[role_name]
-
- def get_role_node_count(self, role):
- """Get the node count for the given role."""
- return int(self.parameter_value(role.node_count_parameter_name,
- 0) or 0)
-
- @cached_property
- def templates(self):
- return tuskarclient(self._request).plans.templates(self.uuid)
-
- @cached_property
- def master_template(self):
- return self.templates.get(MASTER_TEMPLATE_NAME, '')
-
- @cached_property
- def environment(self):
- return self.templates.get(ENVIRONMENT_NAME, '')
-
- @cached_property
- def provider_resource_templates(self):
- template_dict = dict(self.templates)
- del template_dict[MASTER_TEMPLATE_NAME]
- del template_dict[ENVIRONMENT_NAME]
- return template_dict
-
- def parameter_list(self, include_key_parameters=True):
- params = self.parameters
- if not include_key_parameters:
- key_params = []
- for role in self.role_list:
- key_params.extend([role.node_count_parameter_name,
- role.image_parameter_name,
- role.flavor_parameter_name])
- params = [p for p in params if p['name'] not in key_params]
- return [Parameter(p, plan=self) for p in params]
-
- def parameter(self, param_name):
- for parameter in self.parameters:
- if parameter['name'] == param_name:
- return Parameter(parameter, plan=self)
-
- def parameter_value(self, param_name, default=None):
- parameter = self.parameter(param_name)
- if parameter is not None:
- return parameter.value
- return default
-
- def list_generated_parameters(self, with_prefix=True):
- if with_prefix:
- key_format = lambda key: key
- else:
- key_format = strip_prefix
-
- # Get all password like parameters
- return dict(
- (key_format(parameter['name']), parameter)
- for parameter in self.parameter_list()
- if any([
- _should_generate_password(parameter),
- _should_generate_keystone_cert(parameter),
- _should_generate_neutron_control_plane(parameter),
- ])
- )
-
- def _make_keystone_certificates(self, wanted_generated_params):
- generated_params = {}
- for cert_param in KEYSTONE_CERTIFICATE_PARAMS:
- if cert_param in wanted_generated_params.keys():
- # If one of the keystone certificates is not set, we have
- # to generate all of them.
- generate_certificates = True
- break
- else:
- generate_certificates = False
-
- # Generate keystone certificates
- if generate_certificates:
- ca_key_pem, ca_cert_pem = keystone_pki.create_ca_pair()
- signing_key_pem, signing_cert_pem = (
- keystone_pki.create_signing_pair(ca_key_pem, ca_cert_pem))
- generated_params['KeystoneSigningCertificate'] = (
- signing_cert_pem)
- generated_params['KeystoneCACertificate'] = ca_cert_pem
- generated_params['KeystoneSigningKey'] = signing_key_pem
- return generated_params
-
- def make_generated_parameters(self):
- wanted_generated_params = self.list_generated_parameters(
- with_prefix=False)
-
- # Generate keystone certificates
- generated_params = self._make_keystone_certificates(
- wanted_generated_params)
-
- # Generate passwords and control plane id
- for (key, param) in wanted_generated_params.items():
- if _should_generate_password(param):
- generated_params[key] = password_generator()
- elif _should_generate_neutron_control_plane(param):
- generated_params[key] = neutron.network_list(
- self._request, name='ctlplane')[0].id
-
- # Fill all the Tuskar parameters with generated content. There are
- # parameters that has just different prefix, such parameters should
- # have the same values.
- wanted_prefixed_params = self.list_generated_parameters(
- with_prefix=True)
- tuskar_params = {}
-
- for (key, param) in wanted_prefixed_params.items():
- tuskar_params[key] = generated_params[strip_prefix(key)]
-
- return tuskar_params
-
- @property
- def id(self):
- return self.uuid
-
-
-class Role(base.APIResourceWrapper):
- _attrs = ('uuid', 'name', 'version', 'description', 'created')
-
- def __init__(self, apiresource, request=None):
- super(Role, self).__init__(apiresource)
- self._request = request
-
- @classmethod
- @memoized.memoized
- @handle_errors(_("Unable to retrieve overcloud roles"), [])
- def list(cls, request):
- """Return a list of Overcloud Roles in Tuskar
-
- :param request: request object
- :type request: django.http.HttpRequest
-
- :return: list of Overcloud Roles, or an empty list if there
- are none
- :rtype: list of tuskar_ui.api.tuskar.Role
- """
- roles = tuskarclient(request).roles.list()
- return [cls(role, request=request) for role in roles]
-
- @classmethod
- @memoized.memoized
- @handle_errors(_("Unable to retrieve overcloud role"))
- def get(cls, request, role_id):
- """Return the Tuskar Role that matches the ID
-
- :param request: request object
- :type request: django.http.HttpRequest
-
- :param role_id: ID of Role to be retrieved
- :type role_id: int
-
- :return: matching Role, or None if no matching
- Role can be found
- :rtype: tuskar_ui.api.tuskar.Role
- """
- for role in Role.list(request):
- if role.uuid == role_id:
- return role
-
- @classmethod
- @memoized.memoized
- def _roles_by_image(cls, request, plan):
- roles_by_image = {}
-
- for role in Role.list(request):
- image = plan.parameter_value(role.image_parameter_name)
- if image in roles_by_image:
- roles_by_image[image].append(role)
- else:
- roles_by_image[image] = [role]
-
- return roles_by_image
-
- @classmethod
- @handle_errors(_("Unable to retrieve overcloud role"))
- def get_by_image(cls, request, plan, image):
- """Return the Role whose ImageID parameter matches the image.
-
- :param request: request object
- :type request: django.http.HttpRequest
-
- :param plan: associated plan to check against
- :type plan: Plan
-
- :param image: image to be matched
- :type image: Image
-
- :return: matching Role, or None if no matching
- Role can be found
- :rtype: tuskar_ui.api.tuskar.Role
- """
- roles = cls._roles_by_image(request, plan)
- try:
- return roles[image.name]
- except KeyError:
- return []
-
- @classmethod
- @memoized.memoized
- def _roles_by_resource_type(cls, request):
- return {role.provider_resource_type: role
- for role in Role.list(request)}
-
- @classmethod
- @handle_errors(_("Unable to retrieve overcloud role"))
- def get_by_resource_type(cls, request, resource_type):
- roles = cls._roles_by_resource_type(request)
- try:
- return roles[resource_type]
- except KeyError:
- return None
-
- @property
- def provider_resource_type(self):
- return "Tuskar::{0}-{1}".format(self.name, self.version)
-
- @property
- def parameter_prefix(self):
- return "{0}-{1}::".format(self.name, self.version)
-
- @property
- def node_count_parameter_name(self):
- return self.parameter_prefix + 'count'
-
- @property
- def image_parameter_name(self):
- return self.parameter_prefix + 'Image'
-
- @property
- def flavor_parameter_name(self):
- return self.parameter_prefix + 'Flavor'
-
- def image(self, plan):
- image_name = plan.parameter_value(self.image_parameter_name)
- if image_name:
- try:
- return glance.image_list_detailed(
- self._request, filters={'name': image_name})[0][0]
- except (glance_exceptions.HTTPNotFound, IndexError):
- LOG.error("Couldn't obtain image with name %s" % image_name)
- return None
-
- def flavor(self, plan):
- flavor_name = plan.parameter_value(
- self.flavor_parameter_name)
- if flavor_name:
- return flavor.Flavor.get_by_name(self._request, flavor_name)
-
- def parameter_list(self, plan):
- return [p for p in plan.parameter_list() if self == p.role]
-
- def is_valid_for_deployment(self, plan):
- node_count = plan.get_role_node_count(self)
- pending_required_params = list(Parameter.pending_parameters(
- Parameter.required_parameters(self.parameter_list(plan))))
- return not (
- self.image(plan) is None or
- (node_count and self.flavor(plan) is None) or
- pending_required_params
- )
-
- @property
- def id(self):
- return self.uuid
-
-
-class Parameter(base.APIDictWrapper):
-
- _attrs = ['name', 'value', 'default', 'description', 'hidden', 'label',
- 'parameter_type', 'constraints']
-
- def __init__(self, apidict, plan=None):
- super(Parameter, self).__init__(apidict)
- self._plan = plan
-
- @property
- def stripped_name(self):
- return strip_prefix(self.name)
-
- @property
- def plan(self):
- return self._plan
-
- @property
- def role(self):
- if self.plan:
- for role in self.plan.role_list:
- if self.name.startswith(role.parameter_prefix):
- return role
-
- def is_required(self):
- """Boolean: True if parameter is required, False otherwise."""
- return self.default is None
-
- def get_constraint_by_type(self, constraint_type):
- """Returns parameter constraint by it's type.
-
- For available constraint types see HOT Spec:
- http://docs.openstack.org/developer/heat/template_guide/hot_spec.html
- """
-
- constraints_of_type = [c for c in self.constraints
- if c['constraint_type'] == constraint_type]
- if constraints_of_type:
- return constraints_of_type[0]
- else:
- return None
-
- @staticmethod
- def required_parameters(parameters):
- """Yields parameters which are required."""
- for parameter in parameters:
- if parameter.is_required():
- yield parameter
-
- @staticmethod
- def pending_parameters(parameters):
- """Yields parameters which don't have value set."""
- for parameter in parameters:
- if not parameter.value:
- yield parameter
-
- @staticmethod
- def global_parameters(parameters):
- """Yields parameters with name without role prefix."""
- for parameter in parameters:
- if '::' not in parameter.name:
- yield parameter