diff --git a/doc/README.rst b/doc/README.rst new file mode 100644 index 0000000..5201814 --- /dev/null +++ b/doc/README.rst @@ -0,0 +1,8 @@ +OPENSTACK CONFIG ANALYZER DATA MODEL +==================================== + +This document proposes data model which allows to describe any OpenStack +installation. The model includes data regarding physical infrastructure, logical +topology of services and mapping between the two. + + diff --git a/doc/architecture_model.rst b/doc/architecture_model.rst new file mode 100644 index 0000000..4fb19a0 --- /dev/null +++ b/doc/architecture_model.rst @@ -0,0 +1,41 @@ += Arhitecture Data Model + +== Overview + +Architecture data model produced by Joker could be consumed by configuration +validator tool (Dark Knight), by architecture graph (Stencil) and others. + +At some point it should be made convertible into format accepted by deployment +systems (e.g. Fuel or TripleO) which will allow to effectively 'clone' OpenStack +clouds using different deployment applications. + +This model could be reused by Rally project to compare benchmarking results for +different architectures. + +The model can be used to inspect existing clouds for subsequent upgrade. + +The model suits as base for questionaire to assess existing installations for +support contract pricing purposes. + +The model could be used to perform automated/guided hardening of OpenStack +architecture and configuration. + +== Data Format + +This section proposes data model format which allows to describe any OpenStack +installation. The model includes data regarding physical infrastructure, logical +topology of services and mapping between the two. + +Architecture data model could be serialized as JSON or YaML document of the +following format:: + + openstack + nodes + node1 + -param1: value + -param2: value + services + nova + configuration + -param1: value + -param2: value diff --git a/doc/classes_No_Name.dot b/doc/classes_No_Name.dot new file mode 100644 index 0000000..4006669 --- /dev/null +++ b/doc/classes_No_Name.dot @@ -0,0 +1,131 @@ +digraph "classes_No_Name" { +charset="utf-8" +rankdir=BT +"4" [shape="record", label="{KeystoneEndpointsInspection|name : str\ldescription : str\l|inspect()\l}"]; +"6" [shape="record", label="{SchemaParser|conf_ver\lconf_file\lprj_name\l|run()\lvariable_type_detection()\lparse_args()\lgenerate_schema()\l}"]; +"8" [shape="record", label="{VersionTests|\l|test_equility()\ltest_creation_from_components()\ltest_non_equility()\ltest_creation_from_string()\ltest_creation_from_string_with_less_parts()\ltest_creation_from_other_version()\ltest_comparision()\l}"]; +"13" [shape="record", label="{InspectionRequest|username\lnodes\lpassword : NoneType\lprivate_key : NoneType\l|}"]; +"14" [shape="record", label="{InspectionResult|request\lvalue\l|}"]; +"17" [shape="record", label="{TypeValidatorRegistry|\l|register_validator()\lget_validator()\l}"]; +"18" [shape="record", label="{ConfigParameterSchema|name\ldefault : NoneType\lsection : NoneType\lrequired : bool\ldeprecation_message : NoneType\ltype\ldescription : NoneType\l|}"]; +"19" [shape="record", label="{SchemaUpdateRecord|operation\lversion\ldata : NoneType\l|}"]; +"20" [shape="record", label="{TypeValidator|f\l|validate()\l}"]; +"21" [shape="record", label="{ConfigSchema|version\lname\lparameters\lformat\l|get_parameter()\lhas_section()\l}"]; +"22" [shape="record", label="{ConfigSchemaRegistry|\l|register_schema()\lget_schema()\l}"]; +"23" [shape="record", label="{InvalidValueError|\l|}"]; +"24" [shape="record", label="{SchemaBuilder|removals : list\ladds : list\lname\lcurrent_version : NoneType\lcurrent_section : NoneType\ldata\l|section()\lparam()\lversion()\lcommit()\lremove_param()\l}"]; +"25" [shape="record", label="{SchemaError|\l|}"]; +"27" [shape="record", label="{MarkTests|\l|test_merge()\ltest_creation()\l}"]; +"30" [shape="record", label="{ConfigSchemaRegistryTests|\l|test_sample()\l}"]; +"33" [shape="record", label="{IniConfigParserTests|parser\l|test_default_section_name()\ltest_multiline_value()\lparse()\ltest_use_equals_delimiter_if_it_comes_before_colon()\ltest_errors_doesnt_affect_valid_parameters()\ltest_colon_as_delimiter()\ltest_wrapping_value_with_double_quotes_and_trailing_whitespace()\ltest_parsing_with_same_section()\ltest_wrapping_value_with_single_quotes_and_trailing_whitespace()\ltest_hash_in_value_is_part_of_the_value()\ltest_whole_line_comments_starting_with_hash()\ltest_returning_multiple_errors()\lsetUp()\ltest_spaces_in_key_causes_error()\ltest_multiline_value_finished_by_other_parameter()\ltest_use_colon_delimiter_if_it_comes_before_equals_sign()\ltest_wrapping_value_with_single_quotes()\ltest_whole_line_comments_starting_with_semicolon()\ltest_unclosed_section_causes_error()\ltest_parsing_with_different_sections()\lassertAttributes()\ltest_parsing_with_section()\ltest_missing_equals_sign_or_colon_causes_error()\lassertParameter()\ltest_parsing_iolike_source()\ltest_wrapping_value_with_double_quotes()\ltest_multiline_value_finished_by_empty_line()\ltest_parsing()\l}"]; +"35" [shape="record", label="{ValidationLaunchForm|username\lnodes\lprivate_key\llaunch\l|}"]; +"37" [shape="record", label="{FileResource|owner\lpath\lgroup\lcontents\lpermissions\l|}"]; +"38" [shape="record", label="{IssueReporter|issues : list\l|all_issues()\lreport_issue()\l}"]; +"39" [shape="record", label="{CinderSchedulerComponent|config_files : list\lversion\lcomponent : str\lname : str\l|}"]; +"40" [shape="record", label="{MysqlComponent|config_files : list\lversion\lcomponent : str\lname : str\l|}"]; +"41" [shape="record", label="{Service|issues : list\l|report_issue()\lall_issues()\lhost()\lopenstack()\l}"]; +"42" [shape="record", label="{Host|components : list\lname\lnetwork_addresses : list\lid\l|openstack()\ladd_component()\lall_issues()\l}"]; +"43" [shape="record", label="{NovaApiComponent|config_files : list\lversion\lpaste_config_file : NoneType\lcomponent : str\lname : str\l|paste_config()\lall_issues()\l}"]; +"44" [shape="record", label="{KeystoneComponent|config_files : list\lversion\ldb : dict\lcomponent : str\lname : str\l|}"]; +"45" [shape="record", label="{GlanceApiComponent|config_files : list\lversion\lcomponent : str\lname : str\l|}"]; +"46" [shape="record", label="{CinderApiComponent|config_files : list\lversion\lpaste_config_file : NoneType\lcomponent : str\lname : str\l|}"]; +"47" [shape="record", label="{NovaComputeComponent|config_files : list\lversion\lcomponent : str\lname : str\l|}"]; +"48" [shape="record", label="{NovaSchedulerComponent|config_files : list\lversion\lcomponent : str\lname : str\l|}"]; +"49" [shape="record", label="{OpenstackComponent|logger : NoneType, RootLogger\lcomponent : NoneType\l|config()\l}"]; +"50" [shape="record", label="{RabbitMqComponent|\l|}"]; +"51" [shape="record", label="{GlanceRegistryComponent|config_files : list\lversion\lcomponent : str\lname : str\l|}"]; +"52" [shape="record", label="{CinderVolumeComponent|config_files : list\lversion\lrootwrap_config : NoneType\lcomponent : str\lname : str\l|}"]; +"53" [shape="record", label="{Openstack|hosts : list\l|components()\ladd_host()\lall_issues()\l}"]; +"55" [shape="record", label="{StringDictTypeValidatorTests|type_name : str\l|test_single_value()\ltest_empty_value()\ltest_list_of_values()\l}"]; +"56" [shape="record", label="{StringTypeValidatorTests|type_name : str\l|test_validation_always_passes()\ltest_empty_string_passes()\ltest_should_return_same_string_if_valid()\l}"]; +"57" [shape="record", label="{TypeValidatorTestHelper|validator\l|setUp()\lassertInvalid()\lassertValid()\l}"]; +"58" [shape="record", label="{IntegerTypeValidatorTests|type_name : str\l|test_negative_values_are_valid()\ltest_positive_values_are_valid()\ltest_invalid_char_error_contains_proper_column_in_mark()\ltest_invalid_char_error_contains_proper_column_if_leading_whitespaces()\ltest_trailing_whitespace_is_ignored()\ltest_non_digits_are_invalid()\ltest_returns_integer_if_valid()\ltest_zero_is_valid()\ltest_leading_whitespace_is_ignored()\l}"]; +"59" [shape="record", label="{NetworkAddressTypeValidatorTests|type_name : str\l|test_no_prefix_length()\ltest_non_integer_prefix_length()\ltest_prefix_greater_than_32()\ltest_ipv4_network()\ltest_value_with_less_than_4_numbers_separated_by_dots()\ltest_returns_address()\ltest_ipv4_like_string_with_numbers_greater_than_255()\l}"]; +"60" [shape="record", label="{PortTypeValidatorTests|type_name : str\l|test_leading_and_or_trailing_whitespace_is_ignored()\ltest_high_boundary_is_valid()\ltest_returns_integer_if_valid()\ltest_zero_invalid()\ltest_negatives_are_invalid()\ltest_non_digits_are_invalid()\ltest_empty()\ltest_low_boundary_is_valid()\ltest_values_greater_than_65535_are_invalid()\ltest_positive_integer()\l}"]; +"61" [shape="record", label="{BooleanTypeValidatorTests|type_name : str\l|test_True()\ltest_other_values_produce_error()\ltest_False()\l}"]; +"62" [shape="record", label="{HostAndPortTypeValidatorTests|type_name : str\l|test_no_port()\ltest_port_is_not_an_integer()\ltest_port_is_greater_than_65535()\ltest_value_with_less_than_4_numbers_separated_by_dots()\ltest_returns_address()\ltest_ipv4_like_string_with_numbers_greater_than_255()\ltest_ipv4_address()\l}"]; +"63" [shape="record", label="{HostAddressTypeValidatorTests|type_name : str\l|test_value_with_less_than_4_numbers_separated_by_dots()\ltest_host_with_empty_parts()\ltest_mark_should_point_to_incorrect_symbol()\ltest_host_parts_with_invalid_chars()\ltest_host_with_single_host_label()\ltest_host_name()\ltest_returns_address()\ltest_ipv4_like_string_with_numbers_greater_than_255()\ltest_host_that_ends_with_a_hyphen()\ltest_ipv4_address()\ltest_host_part_starting_with_non_letter()\l}"]; +"64" [shape="record", label="{StringListTypeValidatorTests|type_name : str\l|test_single_value()\ltest_empty_value()\ltest_list_of_values()\l}"]; +"70" [shape="record", label="{NodeClient|shell\l|open()\lrun()\l}"]; +"71" [shape="record", label="{OpenstackDiscovery|\l|discover()\l}"]; +"73" [shape="record", label="{KeystoneAuthtokenSettingsInspection|name : str\ldescription : str\l|inspect()\l}"]; +"75" [shape="record", label="{ParseError|\l|}"]; +"77" [shape="record", label="{memoized|cache : dict\lfunc\l|}"]; +"79" [shape="record", label="{ConfigurationTests|default_value : str\lsection : str\lvalue : str\lparam : str\lfullparam\l|test_explicit_default_on_get()\ltest_contains_default()\ltest_is_default_returns_true_if_only_default_value_set()\ltest_normal_overrides_default()\ltest_keys()\ltest_storage()\ltest_cycle_template_substitution_resolves_in_empty_string()\ltest_subsection_keys()\ltest_subsection_getitem()\ltest_subsection_contains()\ltest_subsection_get()\ltest_subsection_items()\ltest_default()\ltest_is_default_returns_false_if_param_missing()\ltest_returns_section_object_even_if_section_doesnot_exist()\ltest_template_substitution()\ltest_parameter_names_containing_sections()\ltest_is_default_returns_false_if_both_values_set()\ltest_getitem()\ltest_contains()\ltest_subsection_setitem()\ltest_subsection_set()\ltest_is_default_returns_false_if_normal_value_set()\ltest_parameter_with_default_section()\ltest_empty()\ltest_getting_raw_values()\ltest_setitem()\ltest_contains_normal()\l}"]; +"81" [shape="record", label="{Configuration|\l|set()\lget()\lkeys()\lsection()\lcontains()\lis_default()\litems()\lset_default()\l}"]; +"82" [shape="record", label="{ConfigSection|name\lparameters\l|}"]; +"83" [shape="record", label="{TextElement|text\l|}"]; +"84" [shape="record", label="{Element|end_mark\lstart_mark\l|}"]; +"85" [shape="record", label="{ComponentConfig|errors : list\lsections : list\lname\l|}"]; +"86" [shape="record", label="{ConfigurationWrapper|state\lconfig\l|}"]; +"87" [shape="record", label="{ConfigParameterName|\l|}"]; +"88" [shape="record", label="{ConfigParameterValue|quotechar : NoneType\lvalue : NoneType\l|}"]; +"89" [shape="record", label="{ConfigSectionName|\l|}"]; +"90" [shape="record", label="{ConfigurationSection|section\lconfig\l|set()\lget()\lkeys()\lcontains()\lis_default()\litems()\lset_default()\l}"]; +"91" [shape="record", label="{ConfigParameter|delimiter\lname\lvalue\l|}"]; +"93" [shape="record", label="{Inspection|\l|all_inspections()\linspect()\l}"]; +"94" [shape="record", label="{MarkedIssue|mark\l|offset_by()\l}"]; +"95" [shape="record", label="{Mark|column : int\lsource\lline : int\l|merge()\l}"]; +"96" [shape="record", label="{Version|parts : list\l|major()\lmaintenance()\lminor()\l}"]; +"97" [shape="record", label="{Error|message\l|}"]; +"98" [shape="record", label="{Issue|message\ltype\lINFO : str\lWARNING : str\lFATAL : str\lERROR : str\l|}"]; +"100" [shape="record", label="{Resource|name\lDIRECTORY : str\lHOST : str\lFILE : str\lSERVICE : str\l|get_contents()\l}"]; +"101" [shape="record", label="{ResourceLocator|\l|find_resource()\l}"]; +"102" [shape="record", label="{FileResource|owner : NoneType\lpath\lgroup : NoneType\lpermissions : NoneType\l|get_contents()\l}"]; +"103" [shape="record", label="{HostResource|interfaces : list\lresource_locator\l|find_resource()\l}"]; +"104" [shape="record", label="{DirectoryResource|owner : NoneType\lgroup : NoneType\lpermissions : NoneType\l|}"]; +"105" [shape="record", label="{FilesystemSnapshot|path\lbasedir\l|get_resource()\l}"]; +"106" [shape="record", label="{ServiceResource|version\lmetadata : dict\l|}"]; +"107" [shape="record", label="{ConfigSnapshotResourceLocator|basedir\l|find_resource()\l}"]; +"110" [shape="record", label="{IniConfigParser|key_value_re\l|parse()\l}"]; +"4" -> "93" [arrowtail="none", arrowhead="empty"]; +"23" -> "94" [arrowtail="none", arrowhead="empty"]; +"25" -> "98" [arrowtail="none", arrowhead="empty"]; +"37" -> "38" [arrowtail="none", arrowhead="empty"]; +"39" -> "49" [arrowtail="none", arrowhead="empty"]; +"40" -> "41" [arrowtail="none", arrowhead="empty"]; +"41" -> "38" [arrowtail="none", arrowhead="empty"]; +"42" -> "38" [arrowtail="none", arrowhead="empty"]; +"43" -> "49" [arrowtail="none", arrowhead="empty"]; +"44" -> "49" [arrowtail="none", arrowhead="empty"]; +"45" -> "49" [arrowtail="none", arrowhead="empty"]; +"46" -> "49" [arrowtail="none", arrowhead="empty"]; +"47" -> "49" [arrowtail="none", arrowhead="empty"]; +"48" -> "49" [arrowtail="none", arrowhead="empty"]; +"49" -> "41" [arrowtail="none", arrowhead="empty"]; +"50" -> "41" [arrowtail="none", arrowhead="empty"]; +"51" -> "49" [arrowtail="none", arrowhead="empty"]; +"52" -> "49" [arrowtail="none", arrowhead="empty"]; +"53" -> "38" [arrowtail="none", arrowhead="empty"]; +"55" -> "57" [arrowtail="none", arrowhead="empty"]; +"56" -> "57" [arrowtail="none", arrowhead="empty"]; +"58" -> "57" [arrowtail="none", arrowhead="empty"]; +"59" -> "57" [arrowtail="none", arrowhead="empty"]; +"60" -> "57" [arrowtail="none", arrowhead="empty"]; +"61" -> "57" [arrowtail="none", arrowhead="empty"]; +"62" -> "57" [arrowtail="none", arrowhead="empty"]; +"63" -> "57" [arrowtail="none", arrowhead="empty"]; +"64" -> "57" [arrowtail="none", arrowhead="empty"]; +"73" -> "93" [arrowtail="none", arrowhead="empty"]; +"75" -> "94" [arrowtail="none", arrowhead="empty"]; +"82" -> "84" [arrowtail="none", arrowhead="empty"]; +"83" -> "84" [arrowtail="none", arrowhead="empty"]; +"85" -> "84" [arrowtail="none", arrowhead="empty"]; +"87" -> "83" [arrowtail="none", arrowhead="empty"]; +"88" -> "83" [arrowtail="none", arrowhead="empty"]; +"89" -> "83" [arrowtail="none", arrowhead="empty"]; +"91" -> "84" [arrowtail="none", arrowhead="empty"]; +"94" -> "98" [arrowtail="none", arrowhead="empty"]; +"102" -> "100" [arrowtail="none", arrowhead="empty"]; +"103" -> "100" [arrowtail="none", arrowhead="empty"]; +"104" -> "100" [arrowtail="none", arrowhead="empty"]; +"106" -> "100" [arrowtail="none", arrowhead="empty"]; +"96" -> "19" [arrowhead="diamond", style="solid", arrowtail="none", fontcolor="green", label="version"]; +"96" -> "21" [arrowhead="diamond", style="solid", arrowtail="none", fontcolor="green", label="version"]; +"96" -> "24" [arrowhead="diamond", style="solid", arrowtail="none", fontcolor="green", label="current_version"]; +"110" -> "33" [arrowhead="diamond", style="solid", arrowtail="none", fontcolor="green", label="parser"]; +"37" -> "43" [arrowhead="diamond", style="solid", arrowtail="none", fontcolor="green", label="paste_config_file"]; +"37" -> "46" [arrowhead="diamond", style="solid", arrowtail="none", fontcolor="green", label="paste_config_file"]; +"37" -> "52" [arrowhead="diamond", style="solid", arrowtail="none", fontcolor="green", label="rootwrap_config"]; +"96" -> "106" [arrowhead="diamond", style="solid", arrowtail="none", fontcolor="green", label="version"]; +} diff --git a/doc/mvp0_demo_preparation_plan.uml b/doc/mvp0_demo_preparation_plan.uml new file mode 100644 index 0000000..e495a93 --- /dev/null +++ b/doc/mvp0_demo_preparation_plan.uml @@ -0,0 +1,28 @@ +frame "Peter" { + [network emulation] + cloud { + [demo scenario] + } +} +frame "Sergey" { + [network emulation] --> [salt bootstrap] + [salt bootstrap] --> [nodes discovery] +} + +frame "Max" { + [config files collector] + [config-inspector] -up-> [demo scenario] +} +frame "Ilya" { + [tripleo-image-elements] --> [os-collect-config] + [tripleo-heat-templates] --> [os-collect-config] +} +frame "Kirill" { + [rules editing engine] <-- [config-inspector] + [rules editing engine] --> [demo scenario] +} +[nodes discovery] --> nodelist +nodelist --> [config files collector] +[config files collector] --> JSON +JSON --> [config-inspector] +[os-collect-config] --> JSON diff --git a/doc/openstack_integration.rst b/doc/openstack_integration.rst new file mode 100644 index 0000000..758e9bf --- /dev/null +++ b/doc/openstack_integration.rst @@ -0,0 +1,45 @@ += VALIDATOR INTEGRATION WITH OPENSTACK + +== Overview + +== Integration Points + +=== TripleO integration points + +:: + +package "Metadata services" { + [Heat] + [CFN] + [EC2] +} + +frame "TripleO" { + package "diskimage-builder" { + [tripleo-image-elements] -- nova.conf + [tripleo-image-elements] -- keystone.conf + [tripleo-image-elements] -- glance.conf + } + [os-collect-config] <-- [Heat] + [os-collect-config] <-- [CFN] + [os-collect-config] <-- + [EC2] + [os-collect-config] + -left-> JSON + JSON --> + [os-refresh-config] +} + +frame "Tuskar" { + [Tuskar] -right- Tuskar_API +} + +[Tuskar] --> HOT +HOT --> [Heat] + +cloud { + [Config Validator] << DarkKnight >> +} + +JSON -up-> [Config Validator] +[tripleo-image-elements] -up-> [Config Validator] diff --git a/doc/packages_No_Name.dot b/doc/packages_No_Name.dot new file mode 100644 index 0000000..f0cee75 --- /dev/null +++ b/doc/packages_No_Name.dot @@ -0,0 +1,81 @@ +digraph "packages_No_Name" { +charset="utf-8" +rankdir=BT +"3" [shape="box", label="ostack_validator.inspections.keystone_endpoints"]; +"5" [shape="box", label="ostack_validator.schemas.schema_generator"]; +"7" [shape="box", label="ostack_validator.test_version"]; +"9" [shape="box", label="ostack_validator.inspections"]; +"10" [shape="box", label="ostack_validator.schemas.nova.v2013_1_3"]; +"11" [shape="box", label="ostack_validator.config_formats"]; +"12" [shape="box", label="ostack_validator.celery"]; +"15" [shape="box", label="ostack_validator"]; +"16" [shape="box", label="ostack_validator.schema"]; +"26" [shape="box", label="ostack_validator.test_mark"]; +"28" [shape="box", label="ostack_validator.schemas.nova.v2013_1"]; +"29" [shape="box", label="ostack_validator.test_config_schema_registry"]; +"31" [shape="box", label="ostack_validator.schemas"]; +"32" [shape="box", label="ostack_validator.config_formats.test_ini"]; +"34" [shape="box", label="ostack_validator.webui"]; +"36" [shape="box", label="ostack_validator.model"]; +"54" [shape="box", label="ostack_validator.test_type_validators"]; +"65" [shape="box", label="ostack_validator.schemas.nova"]; +"66" [shape="box", label="ostack_validator.schemas.keystone.v2013_1_3"]; +"67" [shape="box", label="ostack_validator.schemas.cinder.v2013_1_3"]; +"68" [shape="box", label="ostack_validator.main"]; +"69" [shape="box", label="ostack_validator.discovery"]; +"72" [shape="box", label="ostack_validator.inspections.keystone_authtoken"]; +"74" [shape="box", label="ostack_validator.config_formats.common"]; +"76" [shape="box", label="ostack_validator.utils"]; +"78" [shape="box", label="ostack_validator.test_configuration"]; +"80" [shape="box", label="ostack_validator.config_model"]; +"92" [shape="box", label="ostack_validator.common"]; +"99" [shape="box", label="ostack_validator.resource"]; +"108" [shape="box", label="ostack_validator.schemas.cinder"]; +"109" [shape="box", label="ostack_validator.config_formats.ini"]; +"111" [shape="box", label="ostack_validator.schemas.keystone"]; +"3" -> "92" [arrowtail="none", arrowhead="open"]; +"7" -> "16" [arrowtail="none", arrowhead="open"]; +"9" -> "72" [arrowtail="none", arrowhead="open"]; +"9" -> "3" [arrowtail="none", arrowhead="open"]; +"10" -> "16" [arrowtail="none", arrowhead="open"]; +"11" -> "74" [arrowtail="none", arrowhead="open"]; +"11" -> "109" [arrowtail="none", arrowhead="open"]; +"12" -> "69" [arrowtail="none", arrowhead="open"]; +"12" -> "92" [arrowtail="none", arrowhead="open"]; +"12" -> "12" [arrowtail="none", arrowhead="open"]; +"12" -> "9" [arrowtail="none", arrowhead="open"]; +"15" -> "68" [arrowtail="none", arrowhead="open"]; +"16" -> "92" [arrowtail="none", arrowhead="open"]; +"26" -> "92" [arrowtail="none", arrowhead="open"]; +"28" -> "16" [arrowtail="none", arrowhead="open"]; +"29" -> "16" [arrowtail="none", arrowhead="open"]; +"29" -> "92" [arrowtail="none", arrowhead="open"]; +"31" -> "111" [arrowtail="none", arrowhead="open"]; +"32" -> "109" [arrowtail="none", arrowhead="open"]; +"34" -> "92" [arrowtail="none", arrowhead="open"]; +"34" -> "12" [arrowtail="none", arrowhead="open"]; +"34" -> "36" [arrowtail="none", arrowhead="open"]; +"36" -> "31" [arrowtail="none", arrowhead="open"]; +"36" -> "16" [arrowtail="none", arrowhead="open"]; +"36" -> "92" [arrowtail="none", arrowhead="open"]; +"36" -> "80" [arrowtail="none", arrowhead="open"]; +"36" -> "76" [arrowtail="none", arrowhead="open"]; +"36" -> "11" [arrowtail="none", arrowhead="open"]; +"54" -> "16" [arrowtail="none", arrowhead="open"]; +"54" -> "92" [arrowtail="none", arrowhead="open"]; +"65" -> "28" [arrowtail="none", arrowhead="open"]; +"66" -> "16" [arrowtail="none", arrowhead="open"]; +"67" -> "16" [arrowtail="none", arrowhead="open"]; +"68" -> "15" [arrowtail="none", arrowhead="open"]; +"69" -> "36" [arrowtail="none", arrowhead="open"]; +"69" -> "92" [arrowtail="none", arrowhead="open"]; +"72" -> "92" [arrowtail="none", arrowhead="open"]; +"74" -> "92" [arrowtail="none", arrowhead="open"]; +"78" -> "80" [arrowtail="none", arrowhead="open"]; +"80" -> "92" [arrowtail="none", arrowhead="open"]; +"99" -> "92" [arrowtail="none", arrowhead="open"]; +"108" -> "67" [arrowtail="none", arrowhead="open"]; +"109" -> "80" [arrowtail="none", arrowhead="open"]; +"109" -> "74" [arrowtail="none", arrowhead="open"]; +"111" -> "66" [arrowtail="none", arrowhead="open"]; +}