def get(self): host_url = pecan.request.application_url.rstrip('/') puri = uris.PLANS_URI_STR % host_url pdef_uri = uris.DEPLOY_PARAMS_URI % host_url desc = "Solum CAMP API plans collection resource." handler = plan_handler.PlanHandler(pecan.request.security_context) plan_objs = handler.get_all() p_links = [] for m in plan_objs: p_links.append(common_types.Link(href=uris.PLAN_URI_STR % (host_url, m.uuid), target_name=m.name)) # if there aren't any plans, avoid returning a resource with an # empty plan_links array if len(p_links) > 0: res = model.Plans(uri=puri, name='Solum_CAMP_plans', type='plans', description=desc, parameter_definitions_uri=pdef_uri, plan_links=p_links) else: res = model.Plans(uri=puri, name='Solum_CAMP_plans', type='plans', description=desc, parameter_definitions_uri=pdef_uri) return res
def delete(self, uuid): """Delete this plan.""" handler = (plan_handler.PlanHandler(pecan.request.security_context)) try: handler.delete(uuid) except (db_exc.DBReferenceError, db_exc.DBError): raise exception.PlanStillReferenced(name=uuid)
def patch(self, uuid): """Patch an existing CAMP-style plan.""" handler = (plan_handler.PlanHandler(pecan.request.security_context)) plan_obj = handler.get(uuid) # TODO([email protected]) check if there are any assemblies that # refer to this plan and raise an PlanStillReferenced exception if # there are. if not pecan.request.body or len(pecan.request.body) < 1: raise exception.BadRequest(reason='empty request body') # check to make sure the request has the right Content-Type if (pecan.request.content_type is None or pecan.request.content_type != 'application/json-patch+json'): raise exception.UnsupportedMediaType( name=pecan.request.content_type, method='PATCH') try: patch = jsonpatch.JsonPatch.from_string(pecan.request.body) patched_obj = patch.apply(plan_obj.refined_content()) db_obj = handler.update(uuid, patched_obj) except KeyError: # a key error indicates one of the patch operations is missing a # component raise exception.BadRequest(reason=MAL_PATCH_ERR) except jsonpatch.JsonPatchConflict: raise exception.Unprocessable except jsonpatch.JsonPatchException as jpe: raise JsonPatchProcessingException(reason=six.text_type(jpe)) return fluff_plan(db_obj.refined_content(), db_obj.uuid)
def get_one(self, uuid): """Return the appropriate CAMP-style plan resource.""" handler = (plan_handler.PlanHandler(pecan.request.security_context)) db_obj = handler.get(uuid) plan_dict = fluff_plan(db_obj.refined_content(), db_obj.uuid) return model.Plan(**plan_dict)
def post(self): """Create a new CAMP-style plan.""" if not pecan.request.body or len(pecan.request.body) < 1: raise exception.BadRequest # check to make sure the request has the right Content-Type if (pecan.request.content_type is None or pecan.request.content_type != 'application/x-yaml'): raise exception.UnsupportedMediaType( name=pecan.request.content_type, method='POST') try: yaml_input_plan = yamlutils.load(pecan.request.body) except ValueError as excp: raise exception.BadRequest(reason='Plan is invalid. ' + six.text_type(excp)) camp_version = yaml_input_plan.get('camp_version') if camp_version is None: raise exception.BadRequest( reason='camp_version attribute is missing from submitted Plan') elif camp_version != 'CAMP 1.1': raise exception.BadRequest(reason=UNSUP_VER_ERR % camp_version) # Use Solum's handler as the point of commonality. We can do this # because Solum stores plans in the DB in their JSON form. handler = (plan_handler.PlanHandler(pecan.request.security_context)) model_plan = model.Plan(**yaml_input_plan) # Move any inline Service Specifications to the "services" section. # This avoids an issue where WSME can't properly handle multi-typed # attributes (e.g. 'fulfillment'). It also smoothes out the primary # difference between CAMP plans and Solum plans, namely that Solum # plans don't have inline Service Specifications. for art in model_plan.artifacts: if art.requirements != wsme.Unset: for req in art.requirements: if (req.fulfillment != wsme.Unset and isinstance( req.fulfillment, model.ServiceSpecification)): s_spec = req.fulfillment # if the inline service spec doesn't have an id # generate one if s_spec.id == wsme.Unset: s_spec.id = uuidutils.generate_uuid() # move the inline service spec to the 'services' # section if model_plan.services == wsme.Unset: model_plan.services = [s_spec] else: model_plan.services.append(s_spec) # set the fulfillment to the service spec id req.fulfillment = "id:%s" % s_spec.id db_obj = handler.create( clean_plan(wjson.tojson(model.Plan, model_plan))) plan_dict = fluff_plan(db_obj.refined_content(), db_obj.uuid) pecan.response.status = 201 pecan.response.location = plan_dict['uri'] return plan_dict
def post(self): """Create a new application. There are a number of ways to use this method to create a new application. See Section 6.11 of the CAMP v1.1 specification for an explanation of each. Use the Content-Type of request to determine what the client is trying to do. """ if pecan.request.content_type is None: raise exception.UnsupportedMediaType( name=pecan.request.content_type, method='POST') req_content_type = pecan.request.content_type # deploying by reference uses a JSON payload if req_content_type == 'application/json': payload = pecan.request.body if not payload or len(payload) < 1: raise exception.BadRequest(reason='empty request body') try: json_ref_doc = json.loads(payload) except ValueError as excp: raise exception.BadRequest(reason='JSON object is invalid. ' + six.text_type(excp)) if 'plan_uri' in json_ref_doc: plan_uri_str = json_ref_doc['plan_uri'] # figure out if the plan uri is relative or absolute plan_uri = urllib.parse.urlparse(plan_uri_str) uri_path = plan_uri.path if not plan_uri.netloc: # should be something like "../plans/<uuid>" or # "/camp/v1_1/plans/<uuid> (include Solum plans) if (not uri_path.startswith('../plans/') and not uri_path.startswith('../../../v1/plans/') and not uri_path.startswith('/camp/v1_1/plans/') and not uri_path.startswith('/v1/plans/')): msg = 'plan_uri does not reference a plan resource' raise exception.BadRequest(reason=msg) plan_uuid = plan_uri.path.split('/')[-1] else: # We have an absolute URI. Try to figure out if it refers # to a plan on this Solum instance. Note the following code # does not support URI aliases. A request that contains # a 'plan_uri' with a network location that is different # than network location used to make this request but # which, nevertheless, still refers to this Solum instance # will experience a false negative. This code will treat # that plan as if it existed on another CAMP-compliant # server. if plan_uri_str.startswith(pecan.request.host_url): if (not uri_path.startswith('/camp/v1_1/plans/') and not uri_path.startswith('/v1/plans/')): msg = 'plan_uri does not reference a plan resource' raise exception.BadRequest(reason=msg) plan_uuid = plan_uri.path.split('/')[-1] else: # The plan exists on another server. # TODO(gpilz): support references to plans on other # servers raise exception.NotImplemented() # resolve the local plan by its uuid. this will raise a # ResourceNotFound exception if there is no plan with # this uuid phandler = plan_handler.PlanHandler( pecan.request.security_context) plan_obj = phandler.get(plan_uuid) elif 'pdp_uri' in json_ref_doc: # TODO(gpilz): support references to PDPs raise exception.NotImplemented() else: # must have either 'plan_uri' or 'pdp_uri' msg = 'JSON object must contain either plan_uri or pdp_uri' raise exception.BadRequest(reason=msg) else: # TODO(gpilz): support deploying an application by value raise exception.NotImplemented() # at this point we expect to have a reference to a plan database object # for the plan that will be used to create the application ahandler = assembly_handler.AssemblyHandler( pecan.request.security_context) assem_db_obj = ahandler.create_from_plan(plan_obj) assem_model = model.Assembly.from_db_model(assem_db_obj, pecan.request.host_url) pecan.response.status = 201 pecan.response.location = assem_model.uri return wsme_json.tojson(model.Assembly, assem_model)