def _patch(self, bay_ident, patch): context = pecan.request.context bay = api_utils.get_resource('Cluster', bay_ident) policy.enforce(context, 'bay:update', bay.as_dict(), action='bay:update') bay_to_cluster_attrs = { 'baymodel_id': 'cluster_template_id', 'bay_create_timeout': 'create_timeout' } try: bay_dict = bay.as_dict() new_bay = Bay(**api_utils.apply_jsonpatch(bay_dict, patch)) except api_utils.JSONPATCH_EXCEPTIONS as e: raise exception.PatchError(patch=patch, reason=e) # NOTE(ttsiouts): magnum.objects.Cluster.node_count will be a # property so we won't be able to store it in the object. So # instead of object_what_changed compare the new and the old # clusters. delta = set() for field in new_bay.fields: cluster_field = field if cluster_field in bay_to_cluster_attrs: cluster_field = bay_to_cluster_attrs[field] if cluster_field not in bay_dict: continue if getattr(new_bay, field) != bay_dict[cluster_field]: delta.add(cluster_field) validate_cluster_properties(delta) return bay, new_bay.node_count
def get_one(self, cluster_ident): """Retrieve information about the given Cluster. :param cluster_ident: UUID or logical name of the Cluster. """ context = pecan.request.context if context.is_admin: policy.enforce(context, "cluster:get_one_all_projects", action="cluster:get_one_all_projects") # TODO(flwang): Instead of asking an extra 'all_project's # parameter, currently the design is allowing admin user to list # all clusters from all projects. But the all_tenants is one of # the condition to do project filter in DB API. And it's also used # by periodic tasks. So the could be removed in the future and # a new parameter 'project_id' would be added so that admin user # can list clusters for a particular project. context.all_tenants = True cluster = api_utils.get_resource('Cluster', cluster_ident) policy.enforce(context, 'cluster:get', cluster.as_dict(), action='cluster:get') cluster = Cluster.convert_with_links(cluster) if cluster.status in fields.ClusterStatus.STATUS_FAILED: cluster.faults = self._collect_fault_info(context, cluster) return cluster
def _patch(self, cluster_ident, patch): context = pecan.request.context cluster = api_utils.get_resource('Cluster', cluster_ident) policy.enforce(context, 'cluster:update', cluster.as_dict(), action='cluster:update') try: cluster_dict = cluster.as_dict() new_cluster = Cluster(**api_utils.apply_jsonpatch(cluster_dict, patch)) except api_utils.JSONPATCH_EXCEPTIONS as e: raise exception.PatchError(patch=patch, reason=e) # Update only the fields that have changed for field in objects.Cluster.fields: try: patch_val = getattr(new_cluster, field) except AttributeError: # Ignore fields that aren't exposed in the API continue if patch_val == wtypes.Unset: patch_val = None if cluster[field] != patch_val: cluster[field] = patch_val delta = cluster.obj_what_changed() validation.validate_cluster_properties(delta) return cluster
def post(self, cluster): """Create a new cluster. :param cluster: a cluster within the request body. """ context = pecan.request.context policy.enforce(context, 'cluster:create', action='cluster:create') temp_id = cluster.cluster_template_id cluster_template = objects.BayModel.get_by_uuid(context, temp_id) cluster_dict = cluster.as_dict() attr_validator.validate_os_resources(context, cluster_template.as_dict()) attr_validator.validate_master_count(cluster_dict, cluster_template.as_dict()) cluster_dict['project_id'] = context.project_id cluster_dict['user_id'] = context.user_id # NOTE(yuywz): We will generate a random human-readable name for # cluster if the name is not specified by user. name = cluster_dict.get('name') or \ self._generate_name_for_cluster(context) cluster_dict['name'] = name new_cluster = objects.Bay(context, **cluster_dict) new_cluster.uuid = uuid.uuid4() pecan.request.rpcapi.bay_create_async(new_cluster, cluster.create_timeout) return ClusterID(new_cluster.uuid)
def post(self, baymodel): """Create a new baymodel. :param baymodel: a baymodel within the request body. """ context = pecan.request.context policy.enforce(context, 'baymodel:create', action='baymodel:create') baymodel_dict = baymodel.as_dict() context = pecan.request.context cli = clients.OpenStackClients(context) attr_validator.validate_os_resources(context, baymodel_dict) image_data = attr_validator.validate_image(cli, baymodel_dict['image_id']) baymodel_dict['cluster_distro'] = image_data['os_distro'] baymodel_dict['project_id'] = context.project_id baymodel_dict['user_id'] = context.user_id # check permissions for making baymodel public if baymodel_dict['public']: if not policy.enforce(context, "baymodel:publish", None, do_raise=False): raise exception.BaymodelPublishDenied() new_baymodel = objects.BayModel(context, **baymodel_dict) new_baymodel.create() # Set the HTTP Location Header pecan.response.location = link.build_url('baymodels', new_baymodel.uuid) return BayModel.convert_with_links(new_baymodel)
def post(self, baymodel): """Create a new baymodel. :param baymodel: a baymodel within the request body. """ context = pecan.request.context policy.enforce(context, 'baymodel:create', action='baymodel:create') baymodel_dict = baymodel.as_dict() cli = clients.OpenStackClients(context) attr_validator.validate_os_resources(context, baymodel_dict) image_data = attr_validator.validate_image(cli, baymodel_dict['image_id']) baymodel_dict['cluster_distro'] = image_data['os_distro'] baymodel_dict['project_id'] = context.project_id baymodel_dict['user_id'] = context.user_id # check permissions for making baymodel public if baymodel_dict['public']: if not policy.enforce(context, "baymodel:publish", None, do_raise=False): raise exception.ClusterTemplatePublishDenied() # NOTE(yuywz): We will generate a random human-readable name for # baymodel if the name is not spcified by user. arg_name = baymodel_dict.get('name') name = arg_name or self._generate_name_for_baymodel(context) baymodel_dict['name'] = name new_baymodel = objects.BayModel(context, **baymodel_dict) new_baymodel.create() # Set the HTTP Location Header pecan.response.location = link.build_url('baymodels', new_baymodel.uuid) return BayModel.convert_with_links(new_baymodel)
def _patch(self, bay_ident, patch): context = pecan.request.context bay = api_utils.get_resource('Bay', bay_ident) policy.enforce(context, 'bay:update', bay, action='bay:update') try: bay_dict = bay.as_dict() new_bay = Bay(**api_utils.apply_jsonpatch(bay_dict, patch)) except api_utils.JSONPATCH_EXCEPTIONS as e: raise exception.PatchError(patch=patch, reason=e) # Update only the fields that have changed for field in objects.Bay.fields: try: patch_val = getattr(new_bay, field) except AttributeError: # Ignore fields that aren't exposed in the API continue if patch_val == wtypes.Unset: patch_val = None if bay[field] != patch_val: bay[field] = patch_val delta = bay.obj_what_changed() validate_bay_properties(delta) return bay
def _patch(self, federation_ident, patch): context = pecan.request.context federation = api_utils.get_resource('Federation', federation_ident) policy.enforce(context, 'federation:update', federation.as_dict(), action='federation:update') # NOTE(clenimar): Magnum does not allow one to append items to existing # fields through an `add` operation using HTTP PATCH (please check # `magnum.api.utils.apply_jsonpatch`). In order to perform the join # and unjoin operations, intercept the original JSON PATCH document # and change the operation from either `add` or `remove` to `replace`. patch_path = patch[0].get('path') patch_value = patch[0].get('value') patch_op = patch[0].get('op') if patch_path == '/member_ids': if patch_op == 'add' and patch_value is not None: patch = self._join_wrapper(federation_ident, patch) elif patch_op == 'remove' and patch_value is not None: patch = self._unjoin_wrapper(federation_ident, patch) try: federation_dict = federation.as_dict() new_federation = Federation( **api_utils.apply_jsonpatch(federation_dict, patch)) except api_utils.JSONPATCH_EXCEPTIONS as e: raise exception.PatchError(patch=patch, reason=e) # Retrieve only what changed after the patch. delta = self._update_changed_fields(federation, new_federation) validation.validate_federation_properties(delta) return federation
def get_one(self, cluster_template_ident): """Retrieve information about the given ClusterTemplate. :param cluster_template_ident: UUID or logical name of a ClusterTemplate. """ context = pecan.request.context if context.is_admin: policy.enforce(context, "clustertemplate:get_one_all_projects", action="clustertemplate:get_one_all_projects") # TODO(flwang): Instead of asking an extra 'all_project's # parameter, currently the design is allowing admin user to list # all clusters from all projects. But the all_tenants is one of # the condition to do project filter in DB API. And it's also used # by periodic tasks. So the could be removed in the future and # a new parameter 'project_id' would be added so that admin user # can list clusters for a particular project. context.all_tenants = True cluster_template = api_utils.get_resource('ClusterTemplate', cluster_template_ident) if not cluster_template.public: policy.enforce(context, 'clustertemplate:get', cluster_template.as_dict(), action='clustertemplate:get') return ClusterTemplate.convert_with_links(cluster_template)
def post(self, federation): """Create a new federation. :param federation: a federation within the request body. """ context = pecan.request.context policy.enforce(context, 'federation:create', action='federation:create') federation_dict = federation.as_dict() # Validate `hostcluster_id` hostcluster_id = federation_dict.get('hostcluster_id') attr_validator.validate_federation_hostcluster(hostcluster_id) # Validate `properties` dict. properties_dict = federation_dict.get('properties') attr_validator.validate_federation_properties(properties_dict) federation_dict['project_id'] = context.project_id # If no name is specified, generate a random human-readable name name = (federation_dict.get('name') or self._generate_name_for_federation(context)) federation_dict['name'] = name new_federation = objects.Federation(context, **federation_dict) new_federation.uuid = uuid.uuid4() # TODO(clenimar): remove hard-coded `create_timeout`. pecan.request.rpcapi.federation_create_async(new_federation, create_timeout=15) return FederationID(new_federation.uuid)
def patch(self, bay_ident, patch): """Update an existing bay. :param bay_ident: UUID or logical name of a bay. :param patch: a json PATCH document to apply to this bay. """ context = pecan.request.context bay = api_utils.get_resource('Bay', bay_ident) policy.enforce(context, 'bay:update', bay, action='bay:update') try: bay_dict = bay.as_dict() new_bay = Bay(**api_utils.apply_jsonpatch(bay_dict, patch)) except api_utils.JSONPATCH_EXCEPTIONS as e: raise exception.PatchError(patch=patch, reason=e) # Update only the fields that have changed for field in objects.Bay.fields: try: patch_val = getattr(new_bay, field) except AttributeError: # Ignore fields that aren't exposed in the API continue if patch_val == wtypes.Unset: patch_val = None if bay[field] != patch_val: bay[field] = patch_val delta = bay.obj_what_changed() validate_bay_properties(delta) res_bay = pecan.request.rpcapi.bay_update(bay) return Bay.convert_with_links(res_bay)
def patch(self, cluster_ident, patch): """Update an existing bay. :param cluster_ident: UUID or logical name of a bay. :param patch: a json PATCH document to apply to this bay. """ context = pecan.request.context cluster = api_utils.get_resource('Bay', cluster_ident) policy.enforce(context, 'cluster:update', cluster, action='cluster:update') try: cluster_dict = cluster.as_dict() new_cluster = Cluster(**api_utils.apply_jsonpatch(cluster_dict, patch)) except api_utils.JSONPATCH_EXCEPTIONS as e: raise exception.PatchError(patch=patch, reason=e) # Update only the fields that have changed for field in objects.Bay.fields: try: patch_val = getattr(new_cluster, field) except AttributeError: # Ignore fields that aren't exposed in the API continue if patch_val == wtypes.Unset: patch_val = None if cluster[field] != patch_val: cluster[field] = patch_val delta = cluster.obj_what_changed() validate_bay_properties(delta) pecan.request.rpcapi.bay_update_async(cluster) return ClusterID(cluster.uuid)
def post(self, bay): """Create a new bay. :param bay: a bay within the request body. """ context = pecan.request.context policy.enforce(context, 'bay:create', action='bay:create') baymodel = objects.BayModel.get_by_uuid(context, bay.baymodel_id) attr_validator.validate_os_resources(context, baymodel.as_dict()) attr_validator.validate_master_count(bay.as_dict(), baymodel.as_dict()) bay_dict = bay.as_dict() bay_dict['project_id'] = context.project_id bay_dict['user_id'] = context.user_id # NOTE(yuywz): We will generate a random human-readable name for # bay if the name is not spcified by user. name = bay_dict.get('name') or self._generate_name_for_bay(context) bay_dict['name'] = name new_bay = objects.Bay(context, **bay_dict) res_bay = pecan.request.rpcapi.bay_create(new_bay, bay.bay_create_timeout) # Set the HTTP Location Header pecan.response.location = link.build_url('bays', res_bay.uuid) return Bay.convert_with_links(res_bay)
def post(self, cluster): """Create a new cluster. :param cluster: a cluster within the request body. """ context = pecan.request.context policy.enforce(context, 'cluster:create', action='cluster:create') self._check_cluster_quota_limit(context) temp_id = cluster.cluster_template_id cluster_template = objects.ClusterTemplate.get_by_uuid(context, temp_id) # If keypair not present, use cluster_template value if cluster.keypair is None: cluster.keypair = cluster_template.keypair_id # If docker_volume_size is not present, use cluster_template value if cluster.docker_volume_size == wtypes.Unset: cluster.docker_volume_size = cluster_template.docker_volume_size # If labels is not present, use cluster_template value if cluster.labels == wtypes.Unset: cluster.labels = cluster_template.labels # If master_flavor_id is not present, use cluster_template value if (cluster.master_flavor_id == wtypes.Unset or not cluster.master_flavor_id): cluster.master_flavor_id = cluster_template.master_flavor_id # If flavor_id is not present, use cluster_template value if cluster.flavor_id == wtypes.Unset or not cluster.flavor_id: cluster.flavor_id = cluster_template.flavor_id cluster_dict = cluster.as_dict() attr_validator.validate_os_resources(context, cluster_template.as_dict(), cluster_dict) attr_validator.validate_master_count(cluster_dict, cluster_template.as_dict()) cluster_dict['project_id'] = context.project_id cluster_dict['user_id'] = context.user_id # NOTE(yuywz): We will generate a random human-readable name for # cluster if the name is not specified by user. name = cluster_dict.get('name') or \ self._generate_name_for_cluster(context) cluster_dict['name'] = name cluster_dict['coe_version'] = None cluster_dict['container_version'] = None new_cluster = objects.Cluster(context, **cluster_dict) new_cluster.uuid = uuid.uuid4() pecan.request.rpcapi.cluster_create_async(new_cluster, cluster.create_timeout) return ClusterID(new_cluster.uuid)
def patch(self, cluster_ident): context = pecan.request.context cluster = api_utils.get_resource('Cluster', cluster_ident) policy.enforce(context, 'certificate:rotate_ca', cluster.as_dict(), action='certificate:rotate_ca') if cluster.cluster_template.tls_disabled: raise exception.NotSupported("Rotating the CA certificate on a " "non-TLS cluster is not supported") pecan.request.rpcapi.rotate_ca_certificate(cluster)
def delete(self, baymodel_ident): """Delete a Baymodel. :param baymodel_ident: UUID or logical name of a Baymodel. """ context = pecan.request.context baymodel = api_utils.get_resource('ClusterTemplate', baymodel_ident) policy.enforce(context, 'baymodel:delete', baymodel.as_dict(), action='baymodel:delete') baymodel.destroy()
def delete(self, baymodel_ident): """Delete a baymodel. :param baymodel_ident: UUID or logical name of a baymodel. """ context = pecan.request.context baymodel = api_utils.get_resource('BayModel', baymodel_ident) policy.enforce(context, 'baymodel:delete', baymodel, action='baymodel:delete') baymodel.destroy()
def get_one(self, bay_ident): """Retrieve information about the given bay. :param bay_ident: UUID of a bay or logical name of the bay. """ context = pecan.request.context bay = api_utils.get_resource('Bay', bay_ident) policy.enforce(context, 'bay:get', bay, action='bay:get') return Bay.convert_with_links(bay)
def delete(self, bay_ident): """Delete a bay. :param bay_ident: UUID of a bay or logical name of the bay. """ context = pecan.request.context bay = api_utils.get_resource('Bay', bay_ident) policy.enforce(context, 'bay:delete', bay, action='bay:delete') pecan.request.rpcapi.bay_delete(bay.uuid)
def delete(self, cluster_ident): """Delete a cluster. :param cluster_ident: UUID of cluster or logical name of the cluster. """ context = pecan.request.context cluster = api_utils.get_resource('Cluster', cluster_ident) policy.enforce(context, 'cluster:delete', cluster, action='cluster:delete') pecan.request.rpcapi.cluster_delete_async(cluster.uuid)
def get_one(self, baymodel_ident): """Retrieve information about the given baymodel. :param baymodel_ident: UUID or logical name of a baymodel. """ context = pecan.request.context baymodel = api_utils.get_resource('BayModel', baymodel_ident) if not baymodel.public: policy.enforce(context, 'baymodel:get', baymodel, action='baymodel:get') return BayModel.convert_with_links(baymodel)
def delete(self, federation_ident): """Delete a federation. :param federation_ident: UUID of federation or logical name of the federation. """ context = pecan.request.context federation = api_utils.get_resource('Federation', federation_ident) policy.enforce(context, 'federation:delete', federation.as_dict(), action='federation:delete') pecan.request.rpcapi.federation_delete_async(federation.uuid)
def delete(self, project_id, resource): """Delete Quota for a given project_id and resource. :param project_id: project id. :param resource: resource name. """ context = pecan.request.context policy.enforce(context, 'quota:delete', action='quota:delete') quota_dict = {"project_id": project_id, "resource": resource} quota = objects.Quota(context, **quota_dict) quota.delete()
def get_one(self, bay_ident): """Retrieve CA information about the given bay. :param bay_ident: UUID of a bay or logical name of the bay. """ context = pecan.request.context bay = api_utils.get_resource('Bay', bay_ident) policy.enforce(context, 'certificate:get', bay, action='certificate:get') certificate = pecan.request.rpcapi.get_ca_certificate(bay) return Certificate.convert_with_links(certificate)
def get_one(self, cluster_ident): """Retrieve CA information about the given cluster. :param cluster_ident: UUID of a cluster or logical name of the cluster. """ context = pecan.request.context cluster = api_utils.get_resource('Cluster', cluster_ident) policy.enforce(context, 'certificate:get', cluster.as_dict(), action='certificate:get') certificate = pecan.request.rpcapi.get_ca_certificate(cluster) return Certificate.convert_with_links(certificate)
def delete(self, cluster_template_ident): """Delete a cluster_template. :param cluster_template_ident: UUID or logical name of a cluster_template. """ context = pecan.request.context cluster_template = api_utils.get_resource('BayModel', cluster_template_ident) policy.enforce(context, 'clustertemplate:delete', cluster_template, action='clustertemplate:delete') cluster_template.destroy()
def get_one(self, federation_ident): """Retrieve information about a given Federation. :param federation_ident: UUID or logical name of the Federation. """ context = pecan.request.context federation = api_utils.get_resource('Federation', federation_ident) policy.enforce(context, 'federation:get', federation.as_dict(), action='federation:get') federation = Federation.convert_with_links(federation) return federation
def get_one(self, cluster_template_ident): """Retrieve information about the given clustertemplate. :param cluster_template_ident: UUID or logical name of a clustertemplate. """ context = pecan.request.context cluster_template = api_utils.get_resource('BayModel', cluster_template_ident) if not cluster_template.public: policy.enforce(context, 'clustertemplate:get', cluster_template, action='clustertemplate:get') return ClusterTemplate.convert_with_links(cluster_template)
def patch(self, cluster_template_ident, patch): """Update an existing ClusterTemplate. :param cluster_template_ident: UUID or logic name of a ClusterTemplate. :param patch: a json PATCH document to apply to this ClusterTemplate. """ context = pecan.request.context if context.is_admin: policy.enforce(context, 'clustertemplate:update_all_projects', action='clustertemplate:update_all_projects') context.all_tenants = True cluster_template = api_utils.get_resource('ClusterTemplate', cluster_template_ident) policy.enforce(context, 'clustertemplate:update', cluster_template.as_dict(), action='clustertemplate:update') try: cluster_template_dict = cluster_template.as_dict() new_cluster_template = ClusterTemplate(**api_utils.apply_jsonpatch( cluster_template_dict, patch)) except api_utils.JSONPATCH_EXCEPTIONS as e: raise exception.PatchError(patch=patch, reason=e) new_cluster_template_dict = new_cluster_template.as_dict() attr_validator.validate_os_resources(context, new_cluster_template_dict) # check permissions when updating ClusterTemplate public or hidden flag if (cluster_template.public != new_cluster_template.public or cluster_template.hidden != new_cluster_template.hidden): if not policy.enforce(context, "clustertemplate:publish", None, do_raise=False): raise exception.ClusterTemplatePublishDenied() # Update only the fields that have changed for field in objects.ClusterTemplate.fields: try: patch_val = getattr(new_cluster_template, field) except AttributeError: # Ignore fields that aren't exposed in the API continue if patch_val == wtypes.Unset: patch_val = None if cluster_template[field] != patch_val: cluster_template[field] = patch_val cluster_template.save() return ClusterTemplate.convert_with_links(cluster_template)
def get_all(self, marker=None, limit=None, sort_key='id', sort_dir='asc'): """Retrieve a list of bays. :param marker: pagination marker for large data sets. :param limit: maximum number of resources to return in a single result. :param sort_key: column to sort results by. Default: id. :param sort_dir: direction to sort. "asc" or "desc". Default: asc. """ context = pecan.request.context policy.enforce(context, 'bay:get_all', action='bay:get_all') return self._get_bays_collection(marker, limit, sort_key, sort_dir)
def detail(self, marker=None, limit=None, sort_key='id', sort_dir='asc'): """Retrieve a list of clusters with detail. :param marker: pagination marker for large data sets. :param limit: maximum number of resources to return in a single result. :param sort_key: column to sort results by. Default: id. :param sort_dir: direction to sort. "asc" or "desc". Default: asc. """ context = pecan.request.context policy.enforce(context, 'cluster:detail', action='cluster:detail') # NOTE(lucasagomes): /detail should only work against collections parent = pecan.request.path.split('/')[:-1][-1] if parent != "clusters": raise exception.HTTPNotFound expand = True resource_url = '/'.join(['clusters', 'detail']) return self._get_clusters_collection(marker, limit, sort_key, sort_dir, expand, resource_url)
def upgrade(self, cluster_ident, cluster_upgrade_req): """Upgrade a cluster. :param cluster_ident: UUID of a cluster or logical name of the cluster. """ context = pecan.request.context if context.is_admin: policy.enforce(context, "cluster:upgrade_all_projects", action="cluster:upgrade_all_projects") context.all_tenants = True cluster = api_utils.get_resource('Cluster', cluster_ident) policy.enforce(context, 'cluster:upgrade', cluster, action='cluster:upgrade') new_cluster_template = api_utils.get_resource( 'ClusterTemplate', cluster_upgrade_req.cluster_template) if (cluster_upgrade_req.nodegroup == wtypes.Unset or not cluster_upgrade_req.nodegroup): # NOTE(ttsiouts): If the nodegroup is not specified # reflect the change to the default worker nodegroup nodegroup = cluster.default_ng_worker else: nodegroup = objects.NodeGroup.get(context, cluster.uuid, cluster_upgrade_req.nodegroup) if (new_cluster_template.uuid != cluster.cluster_template_id and not nodegroup.is_default): reason = ("Nodegroup %s can be upgraded only to " "match cluster's template (%s).") reason = reason % (nodegroup.name, cluster.cluster_template.name) raise exception.InvalidClusterTemplateForUpgrade(reason=reason) pecan.request.rpcapi.cluster_upgrade( cluster, new_cluster_template, cluster_upgrade_req.max_batch_size, nodegroup) return ClusterID(cluster.uuid)
def patch(self, baymodel_ident, patch): """Update an existing Baymodel. :param baymodel_ident: UUID or logic name of a Baymodel. :param patch: a json PATCH document to apply to this Baymodel. """ context = pecan.request.context baymodel = api_utils.get_resource('ClusterTemplate', baymodel_ident) policy.enforce(context, 'baymodel:update', baymodel.as_dict(), action='baymodel:update') try: baymodel_dict = baymodel.as_dict() new_baymodel = BayModel( **api_utils.apply_jsonpatch(baymodel_dict, patch)) except api_utils.JSONPATCH_EXCEPTIONS as e: raise exception.PatchError(patch=patch, reason=e) new_baymodel_dict = new_baymodel.as_dict() attr_validator.validate_os_resources(context, new_baymodel_dict) # check permissions when updating baymodel public flag if baymodel.public != new_baymodel.public: if not policy.enforce( context, "baymodel:publish", None, do_raise=False): raise exception.ClusterTemplatePublishDenied() # Update only the fields that have changed for field in objects.ClusterTemplate.fields: try: patch_val = getattr(new_baymodel, field) except AttributeError: # Ignore fields that aren't exposed in the API continue if patch_val == wtypes.Unset: patch_val = None if baymodel[field] != patch_val: baymodel[field] = patch_val baymodel.save() return BayModel.convert_with_links(baymodel)
def resize(self, cluster_ident, cluster_resize_req): """Resize a cluster. :param cluster_ident: UUID of a cluster or logical name of the cluster. """ context = pecan.request.context cluster = api_utils.get_resource('Cluster', cluster_ident) policy.enforce(context, 'cluster:resize', cluster, action='cluster:resize') if (cluster_resize_req.nodegroup == wtypes.Unset or not cluster_resize_req.nodegroup): # NOTE(ttsiouts): If the nodegroup is not specified # reflect the change to the default worker nodegroup nodegroup = cluster.default_ng_worker else: nodegroup = objects.NodeGroup.get(context, cluster.uuid, cluster_resize_req.nodegroup) if nodegroup.role == 'master': # NOTE(ttsiouts): Restrict the resize to worker nodegroups raise exception.MasterNGResizeNotSupported() # NOTE(ttsiouts): Make sure that the new node count is within # the configured boundaries of the selected nodegroup. if nodegroup.min_node_count > cluster_resize_req.node_count: raise exception.NGResizeOutBounds(nodegroup=nodegroup.name, min_nc=nodegroup.min_node_count, max_nc=nodegroup.max_node_count) if (nodegroup.max_node_count and nodegroup.max_node_count < cluster_resize_req.node_count): raise exception.NGResizeOutBounds(nodegroup=nodegroup.name, min_nc=nodegroup.min_node_count, max_nc=nodegroup.max_node_count) pecan.request.rpcapi.cluster_resize_async( cluster, cluster_resize_req.node_count, cluster_resize_req.nodes_to_remove, nodegroup) return ClusterID(cluster.uuid)
def post(self, cluster): """Create a new cluster. :param cluster: a cluster within the request body. """ context = pecan.request.context policy.enforce(context, 'cluster:create', action='cluster:create') self._check_cluster_quota_limit(context) temp_id = cluster.cluster_template_id cluster_template = objects.ClusterTemplate.get_by_uuid( context, temp_id) # If keypair not present, use cluster_template value if cluster.keypair is None: cluster.keypair = cluster_template.keypair_id cluster_dict = cluster.as_dict() attr_validator.validate_os_resources(context, cluster_template.as_dict(), cluster_dict) attr_validator.validate_master_count(cluster_dict, cluster_template.as_dict()) cluster_dict['project_id'] = context.project_id cluster_dict['user_id'] = context.user_id # NOTE(yuywz): We will generate a random human-readable name for # cluster if the name is not specified by user. name = cluster_dict.get('name') or \ self._generate_name_for_cluster(context) cluster_dict['name'] = name cluster_dict['coe_version'] = None cluster_dict['container_version'] = None new_cluster = objects.Cluster(context, **cluster_dict) new_cluster.uuid = uuid.uuid4() pecan.request.rpcapi.cluster_create_async(new_cluster, cluster.create_timeout) return ClusterID(new_cluster.uuid)
def get_one(self, project_id, resource): """Retrieve Quota information for the given project_id. :param id: project id. :param resource: resource name. """ context = pecan.request.context policy.enforce(context, 'quota:get', action='quota:get') if not context.is_admin and project_id != context.project_id: raise exception.NotAuthorized() try: quota = objects.Quota.get_quota_by_project_id_resource( context, project_id, resource) quota = Quota.convert(quota) except exception.QuotaNotFound: # If explicit quota was not set for the project, use default limit quota = Quota(project_id=project_id, hard_limit=CONF.quotas.max_clusters_per_project) return quota
def _post(self, bay): context = pecan.request.context policy.enforce(context, 'bay:create', action='bay:create') baymodel = objects.ClusterTemplate.get_by_uuid(context, bay.baymodel_id) # If docker_volume_size is not present, use baymodel value if bay.docker_volume_size == wtypes.Unset: bay.docker_volume_size = baymodel.docker_volume_size # If labels is not present, use baymodel value if bay.labels is None: bay.labels = baymodel.labels # If master_flavor_id is not present, use baymodel value if bay.master_flavor_id == wtypes.Unset or not bay.master_flavor_id: bay.master_flavor_id = baymodel.master_flavor_id # If flavor_id is not present, use baymodel value if bay.flavor_id == wtypes.Unset or not bay.flavor_id: bay.flavor_id = baymodel.flavor_id bay_dict = bay.as_dict() bay_dict['keypair'] = baymodel.keypair_id attr_validator.validate_os_resources(context, baymodel.as_dict(), bay_dict) attr_validator.validate_master_count(bay.as_dict(), baymodel.as_dict()) bay_dict['project_id'] = context.project_id bay_dict['user_id'] = context.user_id # NOTE(yuywz): We will generate a random human-readable name for # bay if the name is not specified by user. name = bay_dict.get('name') or self._generate_name_for_bay(context) bay_dict['name'] = name bay_dict['coe_version'] = None bay_dict['container_version'] = None new_bay = objects.Cluster(context, **bay_dict) new_bay.uuid = uuid.uuid4() return new_bay
def _patch(self, cluster_ident, patch): context = pecan.request.context if context.is_admin: policy.enforce(context, "cluster:update_all_projects", action="cluster:update_all_projects") context.all_tenants = True cluster = api_utils.get_resource('Cluster', cluster_ident) policy.enforce(context, 'cluster:update', cluster.as_dict(), action='cluster:update') policy.enforce(context, "cluster:update_health_status", action="cluster:update_health_status") try: cluster_dict = cluster.as_dict() new_cluster = Cluster( **api_utils.apply_jsonpatch(cluster_dict, patch)) except api_utils.JSONPATCH_EXCEPTIONS as e: raise exception.PatchError(patch=patch, reason=e) # NOTE(ttsiouts): magnum.objects.Cluster.node_count will be a # property so we won't be able to store it in the object. So # instead of object_what_changed compare the new and the old # clusters. delta = set() for field in new_cluster.fields: if getattr(cluster, field) != getattr(new_cluster, field): delta.add(field) validation.validate_cluster_properties(delta) return (cluster, new_cluster.node_count, new_cluster.health_status, new_cluster.health_status_reason)
def post(self, cluster_id, nodegroup): """Create NodeGroup. :param nodegroup: a json document to create this NodeGroup. """ context = pecan.request.context policy.enforce(context, 'nodegroup:create', action='nodegroup:create') cluster = api_utils.get_resource('Cluster', cluster_id) # Before we start, we need to check that the cluster has an # api_address. If not, just fail. if 'api_address' not in cluster or not cluster.api_address: raise exception.ClusterAPIAddressUnavailable() cluster_ngs = [ng.name for ng in cluster.nodegroups] if nodegroup.name in cluster_ngs: raise exception.NodeGroupAlreadyExists(name=nodegroup.name, cluster_id=cluster.name) _validate_node_count(nodegroup) if nodegroup.role == "master": # Currently we don't support adding master nodegroups. # Keep this until we start supporting it. raise exception.CreateMasterNodeGroup() if nodegroup.image_id is None or nodegroup.image_id == wtypes.Unset: nodegroup.image_id = cluster.cluster_template.image_id if nodegroup.flavor_id is None or nodegroup.flavor_id == wtypes.Unset: nodegroup.flavor_id = cluster.flavor_id if nodegroup.labels is None or nodegroup.labels == wtypes.Unset: nodegroup.labels = cluster.labels nodegroup_dict = nodegroup.as_dict() nodegroup_dict['cluster_id'] = cluster.uuid nodegroup_dict['project_id'] = context.project_id new_obj = objects.NodeGroup(context, **nodegroup_dict) new_obj.uuid = uuid.uuid4() pecan.request.rpcapi.nodegroup_create_async(cluster, new_obj) return NodeGroup.convert(new_obj)
def post(self, bay): """Create a new bay. :param bay: a bay within the request body. """ context = pecan.request.context policy.enforce(context, 'bay:create', action='bay:create') baymodel = objects.BayModel.get_by_uuid(context, bay.baymodel_id) attr_validator.validate_os_resources(context, baymodel.as_dict()) bay_dict = bay.as_dict() bay_dict['project_id'] = context.project_id bay_dict['user_id'] = context.user_id if bay_dict.get('name') is None: bay_dict['name'] = None new_bay = objects.Bay(context, **bay_dict) res_bay = pecan.request.rpcapi.bay_create(new_bay, bay.bay_create_timeout) # Set the HTTP Location Header pecan.response.location = link.build_url('bays', res_bay.uuid) return Bay.convert_with_links(res_bay)
def _get_clusters_collection(self, marker, limit, sort_key, sort_dir, expand=False, resource_url=None): context = pecan.request.context if context.is_admin: if expand: policy.enforce(context, "cluster:detail_all_projects", action="cluster:detail_all_projects") else: policy.enforce(context, "cluster:get_all_all_projects", action="cluster:get_all_all_projects") # TODO(flwang): Instead of asking an extra 'all_project's # parameter, currently the design is allowing admin user to list # all clusters from all projects. But the all_tenants is one of # the condition to do project filter in DB API. And it's also used # by periodic tasks. So the could be removed in the future and # a new parameter 'project_id' would be added so that admin user # can list clusters for a particular project. context.all_tenants = True limit = api_utils.validate_limit(limit) sort_dir = api_utils.validate_sort_dir(sort_dir) marker_obj = None if marker: marker_obj = objects.Cluster.get_by_uuid(pecan.request.context, marker) clusters = objects.Cluster.list(pecan.request.context, limit, marker_obj, sort_key=sort_key, sort_dir=sort_dir) return ClusterCollection.convert_with_links(clusters, limit, url=resource_url, expand=expand, sort_key=sort_key, sort_dir=sort_dir)
def get_all(self, marker=None, limit=None, sort_key='id', sort_dir='asc', bay_ident=None): """Retrieve a list of containers. :param marker: pagination marker for large data sets. :param limit: maximum number of resources to return in a single result. :param sort_key: column to sort results by. Default: id. :param sort_dir: direction to sort. "asc" or "desc". Default: asc. :param bay_indent: UUID or logical name of bay. """ context = pecan.request.context policy.enforce(context, "container:get_all", action="container:get_all") return self._get_containers_collection(marker, limit, sort_key, sort_dir, bay_ident=bay_ident)
def _post(self, bay): context = pecan.request.context policy.enforce(context, 'bay:create', action='bay:create') baymodel = objects.ClusterTemplate.get_by_uuid(context, bay.baymodel_id) bay_dict = bay.as_dict() bay_dict['keypair'] = baymodel.keypair_id attr_validator.validate_os_resources(context, baymodel.as_dict(), bay_dict) attr_validator.validate_master_count(bay.as_dict(), baymodel.as_dict()) bay_dict['project_id'] = context.project_id bay_dict['user_id'] = context.user_id # NOTE(yuywz): We will generate a random human-readable name for # bay if the name is not spcified by user. name = bay_dict.get('name') or self._generate_name_for_bay(context) bay_dict['name'] = name bay_dict['coe_version'] = None bay_dict['container_version'] = None new_bay = objects.Cluster(context, **bay_dict) new_bay.uuid = uuid.uuid4() return new_bay
def get_all(self, marker=None, limit=None, sort_key='id', sort_dir='asc', all_tenants=False): """Retrieve a list of quotas. :param marker: pagination marker for large data sets. :param limit: maximum number of resources to return in a single result. :param sort_key: column to sort results by. Default: id. :param sort_dir: direction to sort. "asc" or "desc". Default: asc. :param all_tenants: a flag to indicate all or current tenant. """ context = pecan.request.context policy.enforce(context, 'quota:get_all', action='quota:get_all') filters = {} if not context.is_admin or not all_tenants: filters = {"project_id": context.project_id} return self._get_quota_collection(marker, limit, sort_key, sort_dir, filters)
def resize(self, cluster_ident, cluster_resize_req): """Resize a cluster. :param cluster_ident: UUID of a cluster or logical name of the cluster. """ context = pecan.request.context cluster = api_utils.get_resource('Cluster', cluster_ident) policy.enforce(context, 'cluster:resize', cluster, action='cluster:resize') if (cluster_resize_req.nodegroup == wtypes.Unset or not cluster_resize_req.nodegroup): # TODO(flwang): The default node group of current cluster could be # extracted by objects.NodeGroups.get_by_uuid or something like # that as long as we have node group support. cluster_resize_req.nodegroup = None pecan.request.rpcapi.cluster_resize_async( cluster, cluster_resize_req.node_count, cluster_resize_req.nodes_to_remove, cluster_resize_req.nodegroup) return ClusterID(cluster.uuid)
def patch(self, cluster_ident, patch): """Update an existing bay. :param cluster_ident: UUID or logical name of a bay. :param patch: a json PATCH document to apply to this bay. """ context = pecan.request.context cluster = api_utils.get_resource('Bay', cluster_ident) policy.enforce(context, 'cluster:update', cluster, action='cluster:update') try: cluster_dict = cluster.as_dict() new_cluster = Cluster( **api_utils.apply_jsonpatch(cluster_dict, patch)) except api_utils.JSONPATCH_EXCEPTIONS as e: raise exception.PatchError(patch=patch, reason=e) # Update only the fields that have changed for field in objects.Bay.fields: try: patch_val = getattr(new_cluster, field) except AttributeError: # Ignore fields that aren't exposed in the API continue if patch_val == wtypes.Unset: patch_val = None if cluster[field] != patch_val: cluster[field] = patch_val delta = cluster.obj_what_changed() validate_bay_properties(delta) pecan.request.rpcapi.bay_update_async(cluster) return ClusterID(cluster.uuid)
def post(self, bay): """Create a new bay. :param bay: a bay within the request body. """ context = pecan.request.context policy.enforce(context, 'bay:create', action='bay:create') baymodel = objects.BayModel.get_by_uuid(context, bay.baymodel_id) attr_validator.validate_os_resources(context, baymodel.as_dict()) bay_dict = bay.as_dict() bay_dict['project_id'] = context.project_id bay_dict['user_id'] = context.user_id # NOTE(yuywz): We will generate a random human-readable name for # bay if the name is not spcified by user. name = bay_dict.get('name') or self._generate_name_for_bay(context) bay_dict['name'] = name new_bay = objects.Bay(context, **bay_dict) res_bay = pecan.request.rpcapi.bay_create(new_bay, bay.bay_create_timeout) # Set the HTTP Location Header pecan.response.location = link.build_url('bays', res_bay.uuid) return Bay.convert_with_links(res_bay)
def get_all(self, project_id=None, type="cluster"): """Retrieve magnum stats. """ context = pecan.request.context policy.enforce(context, 'stats:get_all', action='stats:get_all') allowed_stats = ["cluster"] if type.lower() not in allowed_stats: msg = _("Invalid stats type. Allowed values are '%s'") allowed_str = ','.join(allowed_stats) raise exception.InvalidParameterValue(err=msg % allowed_str) # 1.If the requester is not an admin and trying to request stats for # different tenant, then reject the request # 2.If the requester is not an admin and project_id was not provided, # then return self stats if not context.is_admin: project_id = project_id if project_id else context.project_id if project_id != context.project_id: raise exception.NotAuthorized() stats = objects.Stats.get_cluster_stats(context, project_id) return Stats.convert(stats)
def post(self, cluster_template): """Create a new ClusterTemplate. :param cluster_template: a ClusterTemplate within the request body. """ context = pecan.request.context policy.enforce(context, 'clustertemplate:create', action='clustertemplate:create') cluster_template_dict = cluster_template.as_dict() cli = clients.OpenStackClients(context) attr_validator.validate_os_resources(context, cluster_template_dict) image_data = attr_validator.validate_image( cli, cluster_template_dict['image_id']) cluster_template_dict['cluster_distro'] = image_data['os_distro'] cluster_template_dict['project_id'] = context.project_id cluster_template_dict['user_id'] = context.user_id # check permissions for making cluster_template public or hidden if cluster_template_dict['public'] or cluster_template_dict['hidden']: if not policy.enforce( context, "clustertemplate:publish", None, do_raise=False): raise exception.ClusterTemplatePublishDenied() # NOTE(yuywz): We will generate a random human-readable name for # cluster_template if the name is not specified by user. arg_name = cluster_template_dict.get('name') name = arg_name or self._generate_name_for_cluster_template(context) cluster_template_dict['name'] = name new_cluster_template = objects.ClusterTemplate(context, **cluster_template_dict) new_cluster_template.create() # Set the HTTP Location Header pecan.response.location = link.build_url('clustertemplates', new_cluster_template.uuid) return ClusterTemplate.convert_with_links(new_cluster_template)
def _patch(self, cluster_ident, patch): context = pecan.request.context if context.is_admin: policy.enforce(context, "cluster:update_all_projects", action="cluster:update_all_projects") context.all_tenants = True cluster = api_utils.get_resource('Cluster', cluster_ident) policy.enforce(context, 'cluster:update', cluster.as_dict(), action='cluster:update') policy.enforce(context, "cluster:update_health_status", action="cluster:update_health_status") try: cluster_dict = cluster.as_dict() new_cluster = Cluster( **api_utils.apply_jsonpatch(cluster_dict, patch)) except api_utils.JSONPATCH_EXCEPTIONS as e: raise exception.PatchError(patch=patch, reason=e) # NOTE(ttsiouts): magnum.objects.Cluster.node_count will be a # property so we won't be able to store it in the object. So # instead of object_what_changed compare the new and the old # clusters. delta = set() for field in new_cluster.fields: if getattr(cluster, field) != getattr(new_cluster, field): delta.add(field) validation.validate_cluster_properties(delta) # NOTE(brtknr): cluster.node_count is the size of the whole cluster # which includes non-default nodegroups. However cluster_update expects # node_count to be the size of the default_ng_worker therefore return # this value unless the patch object says otherwise. node_count = cluster.default_ng_worker.node_count for p in patch: if p['path'] == '/node_count': node_count = p.get('value') or new_cluster.node_count return (cluster, node_count, new_cluster.health_status, new_cluster.health_status_reason)
def check_policy_on_container(container, action): context = pecan.request.context policy.enforce(context, action, container, action=action)
def post(self, cluster): """Create a new cluster. :param cluster: a cluster within the request body. """ context = pecan.request.context policy.enforce(context, 'cluster:create', action='cluster:create') self._check_cluster_quota_limit(context) temp_id = cluster.cluster_template_id cluster_template = objects.ClusterTemplate.get_by_uuid(context, temp_id) # If keypair not present, use cluster_template value if cluster.keypair is None: cluster.keypair = cluster_template.keypair_id # If labels is not present, use cluster_template value if cluster.labels == wtypes.Unset or not cluster.labels: cluster.labels = cluster_template.labels else: # If labels are provided check if the user wishes to merge # them with the values from the cluster template. if cluster.merge_labels: labels = cluster_template.labels labels.update(cluster.labels) cluster.labels = labels cinder_csi_enabled = cluster.labels.get('cinder_csi_enabled', True) if (cluster_template.volume_driver == 'cinder' and not strutils.bool_from_string(cinder_csi_enabled)): warnings.warn(self._in_tree_cinder_volume_driver_deprecation_note, DeprecationWarning) LOG.warning(self._in_tree_cinder_volume_driver_deprecation_note) # If floating_ip_enabled is not present, use cluster_template value if cluster.floating_ip_enabled == wtypes.Unset: cluster.floating_ip_enabled = cluster_template.floating_ip_enabled # If master_lb_enabled is not present, use cluster_template value if cluster.master_lb_enabled == wtypes.Unset: cluster.master_lb_enabled = cluster_template.master_lb_enabled attributes = ["docker_volume_size", "master_flavor_id", "flavor_id", "fixed_network", "fixed_subnet"] for attr in attributes: if (getattr(cluster, attr) == wtypes.Unset or not getattr(cluster, attr)): setattr(cluster, attr, getattr(cluster_template, attr)) cluster_dict = cluster.as_dict() attr_validator.validate_os_resources(context, cluster_template.as_dict(), cluster_dict) attr_validator.validate_master_count(cluster_dict, cluster_template.as_dict()) cluster_dict['project_id'] = context.project_id cluster_dict['user_id'] = context.user_id # NOTE(yuywz): We will generate a random human-readable name for # cluster if the name is not specified by user. name = cluster_dict.get('name') or \ self._generate_name_for_cluster(context) cluster_dict['name'] = name cluster_dict['coe_version'] = None cluster_dict['container_version'] = None node_count = cluster_dict.pop('node_count') master_count = cluster_dict.pop('master_count') new_cluster = objects.Cluster(context, **cluster_dict) new_cluster.uuid = uuid.uuid4() pecan.request.rpcapi.cluster_create_async(new_cluster, master_count, node_count, cluster.create_timeout) return ClusterID(new_cluster.uuid)
def _delete(self, bay_ident): context = pecan.request.context bay = api_utils.get_resource('Cluster', bay_ident) policy.enforce(context, 'bay:delete', bay.as_dict(), action='bay:delete') return bay
def check_policy_on_bay(bay_id, action): context = pecan.request.context bay = api_utils.get_resource('Bay', bay_id) policy.enforce(context, action, bay, action=action)
def post(self, cluster): """Create a new cluster. :param cluster: a cluster within the request body. """ context = pecan.request.context policy.enforce(context, 'cluster:create', action='cluster:create') self._check_cluster_quota_limit(context) temp_id = cluster.cluster_template_id cluster_template = objects.ClusterTemplate.get_by_uuid( context, temp_id) # If keypair not present, use cluster_template value if cluster.keypair is None: cluster.keypair = cluster_template.keypair_id # If labels is not present, use cluster_template value if cluster.labels == wtypes.Unset: cluster.labels = cluster_template.labels # If floating_ip_enabled is not present, use cluster_template value if cluster.floating_ip_enabled == wtypes.Unset: cluster.floating_ip_enabled = cluster_template.floating_ip_enabled attributes = [ "docker_volume_size", "master_flavor_id", "flavor_id", "fixed_network", "fixed_subnet" ] for attr in attributes: if (getattr(cluster, attr) == wtypes.Unset or not getattr(cluster, attr)): setattr(cluster, attr, getattr(cluster_template, attr)) cluster_dict = cluster.as_dict() attr_validator.validate_os_resources(context, cluster_template.as_dict(), cluster_dict) attr_validator.validate_master_count(cluster_dict, cluster_template.as_dict()) cluster_dict['project_id'] = context.project_id cluster_dict['user_id'] = context.user_id # NOTE(yuywz): We will generate a random human-readable name for # cluster if the name is not specified by user. name = cluster_dict.get('name') or \ self._generate_name_for_cluster(context) cluster_dict['name'] = name cluster_dict['coe_version'] = None cluster_dict['container_version'] = None node_count = cluster_dict.pop('node_count') master_count = cluster_dict.pop('master_count') new_cluster = objects.Cluster(context, **cluster_dict) new_cluster.uuid = uuid.uuid4() pecan.request.rpcapi.cluster_create_async(new_cluster, master_count, node_count, cluster.create_timeout) return ClusterID(new_cluster.uuid)