seiso-import_master 0.0.6 → 0.0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,15 +1,15 @@
1
1
  ---
2
2
  !binary "U0hBMQ==":
3
3
  metadata.gz: !binary |-
4
- NjdmMzQyNjc3ZTI5YTdmNGM4MGUxZDA4NzYwOTU4YzhmNjBkY2RlYQ==
4
+ ZTRjZDQ3YzYzOWUyOTdjYWY2MTc0ZGIxM2E0YTI4OGQ3ZTAzOWRjNw==
5
5
  data.tar.gz: !binary |-
6
- ZTgzMjBmOTNhMjhmZDA3ZmYzYzUzNTdmNTQzZGI4MThmOWNlMjc5Mg==
6
+ NzNhOWRlNTNjMGQxZGI1ODhhMzExMzY0ZTdkOWQ4OGM4ZDdlM2RmNg==
7
7
  SHA512:
8
8
  metadata.gz: !binary |-
9
- ZThiZGYwODM2ZmQyNGUwNjcxYThlNWJiNjE2ZDhmODE1MzJiMDcwYjU3YmEx
10
- MWQ5OGY0NTY0NDE0Mjg1ZTBjMzUyMTRiMjllZmZiYzM0MTRlMmU4OTQzZmM5
11
- ZTkwMmNhYTU4OWU1MjdiMWJjZjc3YzU1ZDY3YWI2NjU0ZWVjNDc=
9
+ NTY3N2EzOWY2NGY0OGI0N2VmYmMwNTcyMjIwZDI1NDk2Y2MwMDI4NmExZTA4
10
+ NWQ5NjJiYjMyZjFmMzk1Y2ZmNmMyMTU1MTk3ZjA0N2YyZGIyNTY1Yzg0ZWRl
11
+ Y2M3ZTZlODJmZmRmMzBlNjYyNjkxNjAwZGFhZWE4NWQ5MDM1ZTE=
12
12
  data.tar.gz: !binary |-
13
- ZDk2YTQxZmFlZGU3MjFmYTQ1MTlhMzA0YTNhMmQyZjk3Mjg0YjM0ZWZmMTQy
14
- N2IzMDU1ZmE1YzgxMTZjZjg1ZDE2MGU5MWMyYmZjNDQ2YWE3Y2I5YTU4NjE3
15
- OWI2MjJhNmQxYmQ3Y2E3NGFlNjY2MDk1MDZkMGRiM2FhN2NjZGE=
13
+ NTg4MDMxMWMyMjM1NmEwMjUyZWZjZTQ2MDEwYTc0YjM5NjkzMTc4M2VlYWFl
14
+ ZWI5ZWYxNGZlYTU2ZTMxNjNiYWE3YjdhNTZmMGNjMGEyMDE2ZTBhMzU0NGYx
15
+ NjI1NjJiYjc4NWY0ZDFlYjkzN2ExMDVkNzhjYjA5MDg4MTlkMmU=
data/README.md CHANGED
@@ -39,3 +39,17 @@ Or install it yourself as:
39
39
  3. Commit your changes (`git commit -am 'Add some feature'`)
40
40
  4. Push to the branch (`git push origin my-new-feature`)
41
41
  5. Create a new pull request.
42
+
43
+ ## Development
44
+
45
+ Rake tasks available via
46
+
47
+ $ rake tasks
48
+
49
+ In particular, run the unit tests using
50
+
51
+ $ rake test
52
+
53
+ Build and install the gem using Rake:
54
+
55
+ $ rake install
data/Rakefile CHANGED
@@ -5,11 +5,13 @@ task :default => [ :test ]
5
5
 
6
6
  task :tasks do
7
7
  puts "Tasks:"
8
- puts "test : Run unit tests"
8
+ puts "install : Build and install seiso-import_master gem"
9
+ puts "test : Run unit tests"
9
10
  end
10
11
 
11
12
  Rake::TestTask.new do |t|
12
13
  t.libs << 'test'
13
14
  t.test_files = FileList[ 'test/test_*.rb' ]
14
15
  t.verbose = true
16
+ t.warning = true
15
17
  end
@@ -1,9 +1,10 @@
1
1
  require "json"
2
+ require "require_all"
2
3
  require "seiso/connector"
3
4
  require "yaml"
4
- require_relative "import_master/master_item_mapper"
5
5
 
6
- # Seiso namespace module
6
+ require_rel "import_master"
7
+
7
8
  module Seiso
8
9
 
9
10
  # Imports Seiso data master files into Seiso.
@@ -15,31 +16,47 @@ module Seiso
15
16
 
16
17
  # Initializes the importer with a Seiso connector.
17
18
  def initialize(seiso_settings)
18
- @seiso = Seiso::Connector.new(seiso_settings)
19
- @mapper = Seiso::ImportMaster::MasterItemMapper.new
20
19
  @loaders = {
21
20
  'json' => ->(file) { JSON.parse(IO.read(file)) },
22
21
  'yaml' => ->(file) { YAML.load_file file }
23
22
  }
24
- end
25
23
 
26
- def seiso
27
- @seiso
28
- end
24
+ # TODO Add other validators
25
+ @validators = {
26
+ 'nodes' => Validators::NodeValidator.new,
27
+ 'services' => Validators::ServiceValidator.new
28
+ }
29
29
 
30
- def mapper
31
- @mapper
32
- end
30
+ basic_mapper = Mappers::MasterItemMapper.new
31
+ node_mapper = Mappers::NodeMapper.new
32
+ service_mapper = Mappers::ServiceMapper.new
33
+ service_instance_mapper = Mappers::ServiceInstanceMapper.new
34
+
35
+ seiso = Seiso::Connector.new seiso_settings
36
+ uri_factory_v1 = uri_factory_v1 seiso_settings
37
+ uri_factory_v2 = uri_factory_v2 seiso_settings
38
+ rest_connector_v1 = rest_connector(seiso_settings, "application/json")
39
+ rest_connector_v2 = rest_connector(seiso_settings, "application/hal+json")
33
40
 
34
- def loaders
35
- @loaders
41
+ @simple_importer = Importers::SimpleImporter.new(basic_mapper, seiso, uri_factory_v1, rest_connector_v1)
42
+ @importers = {
43
+ 'nodes' => Importers::NodeImporter.new(node_mapper, uri_factory_v1, rest_connector_v1),
44
+ 'services' => Importers::ServiceImporter.new(
45
+ service_mapper,
46
+ basic_mapper,
47
+ uri_factory_v1,
48
+ uri_factory_v2,
49
+ rest_connector_v1,
50
+ rest_connector_v2),
51
+ 'service-instances' => Importers::ServiceInstanceImporter.new(service_instance_mapper, seiso, uri_factory_v1, rest_connector_v1)
52
+ }
36
53
  end
37
-
54
+
38
55
  # Imports a list of master files in order. Legal formats are 'json' (default) and 'yaml'.
39
56
  def import_files(files, format = 'json')
40
57
  loop do
41
58
  file = files.pop
42
- puts "Processing #{file}"
59
+ puts "Processing: #{file}"
43
60
  import_file(file, format)
44
61
  break if files.empty?
45
62
  end
@@ -47,74 +64,43 @@ module Seiso
47
64
 
48
65
  # Imports a data master file. Legal formats are 'json' (default) and 'yaml'.
49
66
  def import_file(file, format = 'json')
50
- loader = loaders[format]
67
+ loader = @loaders[format]
51
68
  raise ArgumentError, "Illegal format: #{format}" if loader.nil?
52
69
  doc = loader.call(file)
53
- import_doc doc
54
- end
55
-
56
- # Imports a data master document.
57
- def import_doc(doc)
58
70
  type = doc['type']
59
- master_items = doc['items']
60
-
61
- # There are some special cases
62
- if type == 'nodes'
63
- si_key = doc['serviceInstance']
64
- do_import_nodes(si_key, master_items)
65
- elsif type == 'services'
66
- do_import_services master_items
67
- elsif type == 'service-instances'
68
- do_import_service_instances master_items
69
- else
70
- do_import_items(type, master_items)
71
- end
71
+ validator = @validators[type]
72
+ validator.validate(doc) unless validator.nil?
73
+ (@importers[type] || @simple_importer).import doc
72
74
  end
73
-
75
+
74
76
  private
75
-
76
- def do_import_items(type, items)
77
- seiso_items = mapper.map_all(type, items)
78
- seiso.post_items(type, seiso_items)
79
- end
80
-
81
- # Imports the nodes, along with their associated IP addresses.
82
- def do_import_nodes(si_key, nodes)
83
- nips = detach_children(nodes, 'node', 'name', 'ipAddresses')
84
- nodes.each do |n|
85
- n['serviceInstance'] = si_key
86
- end
87
- do_import_items('nodes', nodes)
88
- do_import_items('node-ip-addresses', nips)
89
- end
90
-
91
- def do_import_services(services)
92
- doc_links = detach_children(services, 'service', 'key', 'docLinks')
93
- do_import_items('services', services)
94
- # do_import_items('doc-links', docLinks)
95
- doc_links.each { |dl| puts dl.to_json }
77
+
78
+ def uri_factory_v1(settings)
79
+ host = settings['host']
80
+ port = settings['port']
81
+ use_ssl = settings['use_ssl'] || false
82
+ scheme = use_ssl ? "https" : "http"
83
+ base_uri = "#{scheme}://#{host}:#{port}/v1"
84
+ Util::UriFactoryV1.new base_uri
96
85
  end
97
86
 
98
- # Imports the service instances, along with their associated ports and IP address roles.
99
- def do_import_service_instances(service_instances)
100
- ports = detach_children(service_instances, 'serviceInstance', 'key', 'ports')
101
- roles = detach_children(service_instances, 'serviceInstance', 'key', 'ipAddressRoles')
102
- do_import_items('service-instances', service_instances)
103
- do_import_items('service-instance-ports', ports)
104
- do_import_items('ip-address-roles', roles)
87
+ def uri_factory_v2(settings)
88
+ host = settings['host']
89
+ port = settings['port']
90
+ use_ssl = settings['use_ssl'] || false
91
+ scheme = use_ssl ? "https" : "http"
92
+ base_uri = "#{scheme}://#{host}:#{port}/v2"
93
+ Util::UriFactoryV2.new base_uri
105
94
  end
106
-
107
- # Enriches children with a link to the parent, detaches them from the parent, and returns all detached children.
108
- def detach_children(parents, parent_prop, parent_key, child_prop)
109
- all_children = []
110
- parents.each do |p|
111
- children = p[child_prop]
112
- next if children.nil?
113
- children.each { |c| c[parent_prop] = p[parent_key] }
114
- all_children.push(*children)
115
- p.delete child_prop
116
- end
117
- all_children
95
+
96
+ def rest_connector(settings, media_type)
97
+ host = settings['host']
98
+ port = settings['port']
99
+ use_ssl = settings['use_ssl'] || false
100
+ ignore_cert = settings['ignore_cert'] || false
101
+ username = settings['username']
102
+ password = settings['password']
103
+ Util::RestConnector.new(host, port, use_ssl, ignore_cert, username, password, media_type)
118
104
  end
119
105
  end
120
106
  end
@@ -0,0 +1,11 @@
1
+ module Seiso
2
+ class ImportMaster
3
+ module Errors
4
+
5
+ # Author:: Willie Wheeler (mailto:wwheeler@expedia.com)
6
+ # Copyright:: Copyright (c) 2014-2015 Expedia, Inc.
7
+ # License:: Apache 2.0
8
+ class InvalidDocumentError < StandardError; end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,26 @@
1
+ module Seiso
2
+ class ImportMaster
3
+ module Importers
4
+
5
+ # Author:: Willie Wheeler (mailto:wwheeler@expedia.com)
6
+ # Copyright:: Copyright (c) 2014-2015 Expedia, Inc.
7
+ # License:: Apache 2.0
8
+ class BaseImporter
9
+
10
+ # Enriches children with a link to the parent, detaches them from the parent, and returns
11
+ # all detached children.
12
+ def detach_children(parents, parent_prop, parent_key, child_prop)
13
+ all_children = []
14
+ parents.each do |p|
15
+ children = p[child_prop]
16
+ next if children.nil?
17
+ children.each { |c| c[parent_prop] = p[parent_key] }
18
+ all_children.push(*children)
19
+ p.delete child_prop
20
+ end
21
+ all_children
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,79 @@
1
+ require_relative "base_importer"
2
+
3
+ module Seiso
4
+ class ImportMaster
5
+ module Importers
6
+
7
+ # Imports a node document into Seiso.
8
+ #
9
+ # Each node doc corresponds to a service instance. This importer compares the nodes in Seiso with the
10
+ # nodes in the node doc, and removes anything from Seiso that isn't in the node doc. Then it imports
11
+ # the nodes from the node doc into Seiso.
12
+ #
13
+ # Author:: Willie Wheeler (mailto:wwheeler@expedia.com)
14
+ # Copyright:: Copyright (c) 2014-2015 Expedia, Inc.
15
+ # License:: Apache 2.0
16
+ class NodeImporter < BaseImporter
17
+
18
+ def initialize(mapper, uri_factory_v1, rest_connector_v1)
19
+ @mapper = mapper
20
+ @uri_factory_v1 = uri_factory_v1
21
+ @rest_connector_v1 = rest_connector_v1
22
+ end
23
+
24
+ # Imports the node document.
25
+ def import(doc)
26
+ si_key = doc['serviceInstance']
27
+ nodes = doc['items']
28
+ puts "Importing node document for service instance #{si_key}"
29
+
30
+ delete_stale_nodes_from_seiso
31
+
32
+ # Node masters contain the service instance at the top-level, so enrich the nodes themselves
33
+ # with the service instance.
34
+ nodes.each do |n|
35
+ n['serviceInstance'] = si_key
36
+ end
37
+
38
+ nips = detach_children(nodes, 'node', 'name', 'ipAddresses')
39
+
40
+ seiso_nodes = nodes.map { |n| @mapper.seiso_node n }
41
+ nodes_uri = @uri_factory_v1.nodes_uri true
42
+ @rest_connector_v1.post(nodes_uri, seiso_nodes)
43
+
44
+ # import_nips_slowly_without_keepalive nips
45
+ import_nips_quickly_with_keepalive nips
46
+ end
47
+
48
+ private
49
+
50
+ def delete_stale_nodes_from_seiso
51
+ # TODO
52
+ # puts "TODO Delete stale nodes from seiso"
53
+ end
54
+
55
+ def import_nips_slowly_without_keepalive(nips)
56
+ seiso_nips = nips.map { |nip| @mapper.seiso_nip nip }
57
+ seiso_nips.each do |nip|
58
+ node_name = nip['node']['name']
59
+ ip_address = nip['ipAddress']
60
+ nip_uri = @uri_factory_v1.node_ip_address_uri(node_name, ip_address)
61
+ @rest_connector_v1.put(nip_uri, nip)
62
+ end
63
+ end
64
+
65
+ def import_nips_quickly_with_keepalive(nips)
66
+ seiso_nip_pairs = nips.map do |nip|
67
+ node_name = nip['node']
68
+ ip_address = nip['ipAddress']
69
+ {
70
+ "uri" => @uri_factory_v1.node_ip_address_uri(node_name, ip_address),
71
+ "resource" => @mapper.seiso_nip(nip)
72
+ }
73
+ end
74
+ @rest_connector_v1.put_all seiso_nip_pairs
75
+ end
76
+ end
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,135 @@
1
+ require_relative "base_importer"
2
+
3
+ module Seiso
4
+ class ImportMaster
5
+ module Importers
6
+
7
+ # Imports a service document into Seiso.
8
+ #
9
+ # Author:: Willie Wheeler (mailto:wwheeler@expedia.com)
10
+ # Copyright:: Copyright (c) 2014-2015 Expedia, Inc.
11
+ # License:: Apache 2.0
12
+ class ServiceImporter < BaseImporter
13
+
14
+ def initialize(
15
+ service_mapper,
16
+ basic_mapper,
17
+ uri_factory_v1,
18
+ uri_factory_v2,
19
+ rest_connector_v1,
20
+ rest_connector_v2)
21
+
22
+ @service_mapper = service_mapper
23
+ @basic_mapper = basic_mapper
24
+ @uri_factory_v1 = uri_factory_v1
25
+ @uri_factory_v2 = uri_factory_v2
26
+ @rest_connector_v1 = rest_connector_v1
27
+ @rest_connector_v2 = rest_connector_v2
28
+ end
29
+
30
+ # Imports the service document
31
+ def import(doc)
32
+ services = doc['items']
33
+ puts "Importing service document"
34
+
35
+ doc_links = detach_children(services, 'service', 'key', 'docLinks')
36
+
37
+ seiso_services = services.map { |s| @service_mapper.seiso_service s }
38
+ services_uri = @uri_factory_v1.services_uri true
39
+ @rest_connector_v1.post(services_uri, seiso_services)
40
+
41
+ import_doc_links doc_links
42
+ end
43
+
44
+ private
45
+
46
+ def import_doc_links(doc_links)
47
+ doc_links_by_service = group_doc_links_by_service doc_links
48
+
49
+ doc_links_by_service.each do |service_key, doc_links|
50
+ doc_links_uri = @uri_factory_v2.doc_links_uri service_key
51
+
52
+ # Get service's doc links so we can
53
+ # 1) Delete stale doc links
54
+ # 2) Avoid duplicate imports
55
+ seiso_doc_links_response = @rest_connector_v2.get(doc_links_uri)
56
+ seiso_doc_links = JSON.parse(seiso_doc_links_response.body)
57
+
58
+ to_delete = seiso_doc_links_to_delete(doc_links, seiso_doc_links)
59
+
60
+ # Delete stale doc links
61
+ to_delete.each do |seiso_doc_link|
62
+ href = seiso_doc_link['_links']['self']['href']
63
+ uri = URI.parse href
64
+ @rest_connector_v2.delete(uri)
65
+ end
66
+
67
+ # Classify remaining links as post or put
68
+ to_post = []
69
+ to_put = []
70
+ doc_links.each do |sdm_doc_link|
71
+ sdm_title = sdm_doc_link['title']
72
+ post_it = true
73
+ seiso_doc_links.each do |seiso_doc_link|
74
+ seiso_title = seiso_doc_link['title']
75
+ if sdm_title == seiso_title
76
+ href = seiso_doc_link['_links']['self']['href']
77
+ uri = URI.parse href
78
+ new_seiso_doc_link = @basic_mapper.map_one('doc-links', sdm_doc_link)
79
+ to_put << {
80
+ "uri" => uri,
81
+ "resource" => new_seiso_doc_link
82
+ }
83
+ post_it = false
84
+ break
85
+ end
86
+ end
87
+ to_post << sdm_doc_link if post_it
88
+ end
89
+
90
+ # Post new links
91
+ to_post.each do |sdm_doc_link|
92
+ seiso_doc_link = @basic_mapper.map_one('doc-links', sdm_doc_link)
93
+ @rest_connector_v2.post(doc_links_uri, seiso_doc_link)
94
+ end
95
+
96
+ # Put existing links
97
+ @rest_connector_v2.put_all(to_put)
98
+ end
99
+ end
100
+
101
+ def group_doc_links_by_service(doc_links)
102
+ doc_links_by_service = {}
103
+ doc_links.each do |dl|
104
+ service_key = dl['service']
105
+ service_doc_links = doc_links_by_service[service_key]
106
+ if service_doc_links.nil?
107
+ service_doc_links = []
108
+ doc_links_by_service[service_key] = service_doc_links
109
+ end
110
+ service_doc_links << dl
111
+ end
112
+ doc_links_by_service
113
+ end
114
+
115
+ def seiso_doc_links_to_delete(sdm_doc_links, seiso_doc_links)
116
+ to_delete = []
117
+ seiso_doc_links.each do |seiso_doc_link|
118
+ seiso_title = seiso_doc_link['title']
119
+ delete_it = true
120
+ sdm_doc_links.each do |sdm_doc_link|
121
+ sdm_title = sdm_doc_link['title']
122
+ if sdm_title == seiso_title
123
+ delete_it = false
124
+ break
125
+ end
126
+ end
127
+ to_delete << seiso_doc_link if delete_it
128
+ end
129
+ to_delete
130
+ end
131
+
132
+ end # class ServiceImporter
133
+ end # module Importers
134
+ end # class ImportMaster
135
+ end # module Seiso