Skip to content

Commit

Permalink
Fix #74: Update the constraints that define how to stack components.
Browse files Browse the repository at this point in the history
  • Loading branch information
fchauvel committed Sep 23, 2019
1 parent 2476bca commit 5014200
Show file tree
Hide file tree
Showing 5 changed files with 192 additions and 32 deletions.
9 changes: 8 additions & 1 deletion camp/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,15 @@ def generate(self, arguments):
try:
model = self._load_model()
configurations = self._generate_configurations(arguments, model)

count = 0
for index, each_configuration in enumerate(configurations, 1):
self._save(index, each_configuration)
count += 1

if count == 0:
self._ui.no_configuration_generated()


except InvalidYAMLModel as error:
self._ui.invalid_yaml_model(error)
Expand Down Expand Up @@ -108,8 +115,8 @@ def _generate_configurations(self, arguments, model):


def _save(self, index, configuration):
self._output.save_as_graphviz(index, configuration)
yaml_file = self._output.save_as_yaml(index, configuration)
self._output.save_as_graphviz(index, configuration)
self._ui.new_configuration(index, configuration, yaml_file)


Expand Down
7 changes: 7 additions & 0 deletions camp/data/metamodel.yml
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,13 @@
type: Partner
multiple: true
mandatory: true

# Hold the transitive closure all underlying feature provider
- name: stack
type: CInstance
mandatory: true
multiple: true

- name: use_feature
type: CInstance
- name: configuration
Expand Down
112 changes: 83 additions & 29 deletions camp/generate.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ def _cover(self):

def _solve(self):
z3_solution = cast_all_objects(self._solver.model())
#import pprint; pprint.pprint(z3_solution)

self._solver.add(self._context.evaluate(self._as_constraint(z3_solution)))
return self._extract_from(z3_solution)
Expand Down Expand Up @@ -146,6 +147,7 @@ def _extract_from(self, z3_solution):
if "use_feature" in item and item["use_feature"]:
provider = result.resolve(item["use_feature"])
instance.feature_provider = provider

if "partners" in item:
providers = [result.resolve(z3_solution[each]["endpoint"]) \
for each in item["partners"]]
Expand Down Expand Up @@ -399,8 +401,8 @@ def coverage_gain(self):


INTEGRITY_VARIABLES = [
("CInstance", ["ci", "ci1", "ci2", "spi"]),
("Feature", ["fr", "fp"]),
("CInstance", ["ci", "ci1", "ci2", "ci3", "ci4", "ci5", "spi"]),
("Feature", ["fr", "fp", "f1", "f2", "f3"]),
("Partner", ["partner"]),
("Service", ["service", "sr", "sp"]),
("Variable", ["var"]),
Expand All @@ -415,30 +417,102 @@ def coverage_gain(self):
CInstance.all_instances().count() > 0
""",

# Cannot be deploy on itself
# -----
# DEFINITION of CInstance::stack

# No feature provider, no stack
"""
CInstance.forall(ci1,
Implies(
ci1.use_feature.undefined(),
ci1.stack.count() == 0))
""",


# The feature provider must be in the stack
"""
CInstance.forall(ci1,
Implies(
Not(ci1.use_feature.undefined()),
ci1.stack.exists(ci2, ci2 == ci1.use_feature)))
""",


# Stack Correctness: Any element in the stack is either the
# underlying feature provider or somewhere further down the stack
"""
CInstance.forall(ci1,
ci1.stack.forall(ci2,
Or(ci1.use_feature == ci2,
ci1.use_feature.stack.exists(ci3, ci3 == ci2))))
""",


# Stack Completness: Every element in my stack is also in the stack of
# the element above me in the stack
"""
CInstance.forall(ci1,
CInstance.forall(ci2,
Implies(
ci2.use_feature == ci1,
And(
ci2.stack.exists(ci3, ci3 == ci1),
ci1.stack.forall(ci4,
ci2.stack.exists(ci5, ci5 == ci4))))))
""",


# No cycle in the deployment structure
"""
CInstance.forall(ci, Not(ci["use_feature"] == ci))
CInstance.forall(ci1,
Not(ci1.stack.exists(ci2, ci2 == ci1)))
""",


# Service bindings

# An instance cannot use its own services
"""
CInstance.forall(ci, Not(ci.partners.exists(
partner, partner.endpoint == ci)))
""",

# Can only deploy on something that provides the required features

# STACK CONSTRUCTION THROUGH FEATURES

# Instances that do not require features cannot have a feature_provider
"""
CInstance.forall(ci,
Implies(
ci.definition.require_features.count() == 0,
ci.use_feature.undefined()))
""",


# Instances that do require features must have one feature_provider that
# provides all the required features
"""
CInstance.forall(ci, ci["definition"]["require_features"].forall(
fr, ci.use_feature["definition"].provide_features.exists(fp, fp == fr)))
CInstance.forall(ci1,
ci1.definition.require_features.forall(f1,
CInstance.exists(ci2,
And(
ci2 == ci1.use_feature,
Or(
ci2.definition.provide_features.exists(f2, f2 == f1),
ci2.stack.exists(ci3,
ci3.definition.provide_features.exists(f3, f3 == f1)))))))
""",

# All partner shall connect to an endpoint that provides the requested service

# All partner shall connect to an endpoint that provides the requested
# service
"""
Partner.forall(partner,
partner.endpoint.definition.provide_services.exists(service,
service == partner.service))
""",


# Instances that do not require services cannot have any
# service provider
"""
Expand All @@ -447,34 +521,14 @@ def coverage_gain(self):
ci["partners"].count() == 0))
""",

# Instances that do not require features cannot have a
# feature_provider
"""
CInstance.forall(ci, Implies(ci["definition"]["require_features"].count() == 0,
ci["use_feature"].undefined()))
""",

# Instances that do require features must have one
# feature_provider
"""
CInstance.forall(ci, Implies(ci["definition"]["require_features"].count() > 0,
Not(ci["use_feature"].undefined())))
""",

# All provided features must be used
"""
CInstance.forall(ci1,
Implies(ci1.definition.provide_features.count() > 0,
CInstance.exists(ci2, ci2.use_feature == ci1)))
""",

# Only one pending service
"""
CInstance.filter(ci1,
And([ci1.definition.provide_services.count() > 0,
CInstance.forall(ci2, ci2.partners.forall(partner,
partner.endpoint != ci1))])).count() == 1
"""
""",

# No pending instances
# """
Expand Down
4 changes: 4 additions & 0 deletions camp/ui.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,10 @@ def new_configuration(self, index, configuration, path):
self._summarize(configuration)


def no_configuration_generated(self):
self._print("\nError: No configuration generated! Is the model correct?")


def configurations_loaded(self, path):
self._print("Loading configurations from '{path}' ...", path=path)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@



class FilesAreGenerated(TestCase):
class ConfigurationsAreGenerated(TestCase):


def test_single_component(self):
Expand Down Expand Up @@ -66,6 +66,66 @@ def test_variables(self):


def test_stack(self):
self.prepare_sample(
"components:\n"
" app:\n"
" provides_services: [ Awesome ]\n"
" requires_features: [ JRE ]\n"
" jre:\n"
" provides_features: [ JRE ]\n"
"goals:\n"
" running:\n"
" - Awesome\n")

self.invoke_camp_generate()

self.assert_configuration_count_is(1)


def test_stack_with_three_components(self):
self.prepare_sample(
"components:\n"
" app:\n"
" provides_services: [ Awesome ]\n"
" requires_features: [ ServletContainer ]\n"
" tomcat:\n"
" provides_features: [ ServletContainer ]\n"
" requires_features: [ JRE ]\n"
" jre:\n"
" provides_features: [ JRE ]\n"
"goals:\n"
" running:\n"
" - Awesome\n")

self.invoke_camp_generate()

self.assert_configuration_count_is(1)


def test_stack_with_four_components(self):
self.prepare_sample(
"components:\n"
" app:\n"
" provides_services: [ Awesome ]\n"
" requires_features: [ ServletContainer ]\n"
" tomcat:\n"
" provides_features: [ ServletContainer ]\n"
" requires_features: [ JRE ]\n"
" jre:\n"
" provides_features: [ JRE ]\n"
" requires_features: [ Linux ]\n"
" ubuntu:\n"
" provides_features: [ Linux ]\n"
"goals:\n"
" running:\n"
" - Awesome\n")

self.invoke_camp_generate()

self.assert_configuration_count_is(1)


def test_stack_with_variable(self):
self.prepare_sample(
"components:\n"
" app:\n"
Expand All @@ -85,6 +145,32 @@ def test_stack(self):
self.assert_configuration_count_is(2)


# See Issue 74
def test_stack_with_side_by_side_components(self):
self.prepare_sample(
"components:\n"
" app:\n"
" provides_services: [ Awesome ]\n"
" requires_features: [ Python, HttpProxy ]\n"
" apache:\n"
" provides_features: [ HttpProxy ]\n"
" requires_features: [ Linux ]\n"
" django:\n"
" provides_features: [ Python ]\n"
" requires_features: [ Linux ]\n"
" ubuntu:\n"
" provides_features: [ Linux ]\n"
"\n"
"goals:\n"
" running:\n"
" - Awesome\n")

self.invoke_camp_generate()

self.assert_configuration_count_is(2)



def test_orchestrations(self):
self.prepare_sample(
"components:\n"
Expand All @@ -98,7 +184,7 @@ def test_orchestrations(self):
" provides_services: [ DB ]\n"
" postgresql:\n"
" provides_services: [ DB ]\n"
" \n"
"\n"
"goals:\n"
" running:\n"
" - Awesome\n")
Expand All @@ -108,6 +194,8 @@ def test_orchestrations(self):
self.assert_configuration_count_is(2)




def prepare_sample(self, sample):
self._working_directory = join(mkdtemp(prefix="camp_"), "generate")
makedirs(self._working_directory)
Expand Down

0 comments on commit 5014200

Please sign in to comment.