277 # Handle the cloud-init regular meta
278 if not md['local-hostname']:
279 md['local-hostname'] = md['instance-id']
280
281 ud = None
282 if md['user-data']:
283 ud = md['user-data']
284
285 if not md['vendor-data']:
286 md['vendor-data'] = BUILTIN_VENDOR_DATA % {
287 'user_script': user_script,
288 'operator_script': operator_script,
289 'per_boot_d': os.path.join(self.paths.get_cpath("scripts"),
290 'per-boot'),
291 }
292
293 self.metadata = util.mergemanydict([md, self.metadata])
294 self.userdata_raw = ud
295 self.vendordata_raw = md['vendor-data']
296 self.network_data = md['network-data']
297
298 self._set_provisioned()
299 return True
300
301 def device_name_to_device(self, name):
302 return self.ds_cfg['disk_aliases'].get(name)
303
304 def get_config_obj(self):
305 if self.smartos_type == SMARTOS_ENV_KVM:
306 return BUILTIN_CLOUD_CONFIG
307 return {}
308
309 def get_instance_id(self):
310 return self.metadata['instance-id']
311
312 @property
313 def network_config(self):
314 if self._network_config is None:
315 if self.network_data is not None:
316 self._network_config = (
317 convert_smartos_network_data(
318 network_data=self.network_data,
319 dns_servers=self.metadata['dns_servers'],
320 dns_domain=self.metadata['dns_domain']))
321 return self._network_config
322
323
324 class JoyentMetadataFetchException(Exception):
325 pass
326
327
328 class JoyentMetadataTimeoutException(JoyentMetadataFetchException):
329 pass
330
331
332 class JoyentMetadataClient(object):
333 """
334 A client implementing v2 of the Joyent Metadata Protocol Specification.
335
336 The full specification can be found at
337 http://eng.joyent.com/mdata/protocol.html
338 """
339 line_regex = re.compile(
340 r'V2 (?P<length>\d+) (?P<checksum>[0-9a-f]+)'
772 # SDC LX-Brand Zones lack dmidecode (no /dev/mem) but
773 # report 'BrandZ virtual linux' as the kernel version
774 if uname_version is None:
775 uname_version = uname[3]
776 if uname_version.lower() == 'brandz virtual linux':
777 return SMARTOS_ENV_LX_BRAND
778
779 if product_name is None:
780 system_type = util.read_dmi_data("system-product-name")
781 else:
782 system_type = product_name
783
784 if system_type and 'smartdc' in system_type.lower():
785 return SMARTOS_ENV_KVM
786
787 return None
788
789
790 # Convert SMARTOS 'sdc:nics' data to network_config yaml
791 def convert_smartos_network_data(network_data=None,
792 dns_servers=None, dns_domain=None):
793 """Return a dictionary of network_config by parsing provided
794 SMARTOS sdc:nics configuration data
795
796 sdc:nics data is a dictionary of properties of a nic and the ip
797 configuration desired. Additional nic dictionaries are appended
798 to the list.
799
800 Converting the format is straightforward though it does include
801 duplicate information as well as data which appears to be relevant
802 to the hostOS rather than the guest.
803
804 For each entry in the nics list returned from query sdc:nics, we
805 create a type: physical entry, and extract the interface properties:
806 'mac' -> 'mac_address', 'mtu', 'interface' -> 'name'. The remaining
807 keys are related to ip configuration. For each ip in the 'ips' list
808 we create a subnet entry under 'subnets' pairing the ip to a one in
809 the 'gateways' list.
810 """
811
812 valid_keys = {
813 'physical': [
814 'mac_address',
815 'mtu',
816 'name',
817 'params',
818 'subnets',
819 'type',
820 ],
821 'subnet': [
822 'address',
823 'broadcast',
824 'dns_nameservers',
825 'dns_search',
826 'metric',
827 'pointopoint',
828 'routes',
829 'scope',
830 'type',
831 ],
832 }
833
834 if dns_servers:
835 if not isinstance(dns_servers, (list, tuple)):
836 dns_servers = [dns_servers]
837 else:
838 dns_servers = []
839
840 if dns_domain:
841 if not isinstance(dns_domain, (list, tuple)):
842 dns_domain = [dns_domain]
843 else:
844 dns_domain = []
845
846 def is_valid_ipv4(addr):
847 return '.' in addr
848
849 def is_valid_ipv6(addr):
850 return ':' in addr
851
877 })
878
879 proto = 'ipv4' if is_valid_ipv4(ip) else 'ipv6'
880 # Only use gateways for 'primary' nics
881 if 'primary' in nic and nic.get('primary', False):
882 # the ips and gateways list may be N to M, here
883 # we map the ip index into the gateways list,
884 # and handle the case that we could have more ips
885 # than gateways. we only consume the first gateway
886 if not pgws[proto]['gw']:
887 gateways = [gw for gw in nic.get('gateways', [])
888 if pgws[proto]['match'](gw)]
889 if len(gateways):
890 pgws[proto]['gw'] = gateways[0]
891 subnet.update({'gateway': pgws[proto]['gw']})
892
893 subnets.append(subnet)
894 cfg.update({'subnets': subnets})
895 config.append(cfg)
896
897 if dns_servers:
898 config.append(
899 {'type': 'nameserver', 'address': dns_servers,
900 'search': dns_domain})
901
902 return {'version': 1, 'config': config}
903
904
905 # Used to match classes to dependencies
906 datasources = [
907 (DataSourceSmartOS, (sources.DEP_FILESYSTEM, )),
908 ]
909
910
911 # Return a list of data sources that match this set of dependencies
912 def get_datasource_list(depends):
913 return sources.list_from_depends(depends, datasources)
914
915
916 if __name__ == "__main__":
917 import sys
918 jmc = jmc_client_factory()
919 if jmc is None:
920 print("Do not appear to be on smartos.")
921 sys.exit(1)
922 if len(sys.argv) == 1:
923 keys = (list(SMARTOS_ATTRIB_JSON.keys()) +
924 list(SMARTOS_ATTRIB_MAP.keys()) + ['network_config'])
925 else:
926 keys = sys.argv[1:]
927
928 def load_key(client, key, data):
929 if key in data:
930 return data[key]
931
932 if key in SMARTOS_ATTRIB_JSON:
933 keyname = SMARTOS_ATTRIB_JSON[key]
934 data[key] = client.get_json(keyname)
935 elif key == "network_config":
936 for depkey in ('network-data', 'dns_servers', 'dns_domain'):
937 load_key(client, depkey, data)
938 data[key] = convert_smartos_network_data(
939 network_data=data['network-data'],
940 dns_servers=data['dns_servers'],
941 dns_domain=data['dns_domain'])
942 else:
943 if key in SMARTOS_ATTRIB_MAP:
944 keyname, strip = SMARTOS_ATTRIB_MAP[key]
945 else:
946 keyname, strip = (key, False)
947 data[key] = client.get(keyname, strip=strip)
948
949 return data[key]
950
951 data = {}
952 for key in keys:
953 load_key(client=jmc, key=key, data=data)
954
955 print(json.dumps(data, indent=1, sort_keys=True,
956 separators=(',', ': ')))
957
958 # vi: ts=4 expandtab
|
277 # Handle the cloud-init regular meta
278 if not md['local-hostname']:
279 md['local-hostname'] = md['instance-id']
280
281 ud = None
282 if md['user-data']:
283 ud = md['user-data']
284
285 if not md['vendor-data']:
286 md['vendor-data'] = BUILTIN_VENDOR_DATA % {
287 'user_script': user_script,
288 'operator_script': operator_script,
289 'per_boot_d': os.path.join(self.paths.get_cpath("scripts"),
290 'per-boot'),
291 }
292
293 self.metadata = util.mergemanydict([md, self.metadata])
294 self.userdata_raw = ud
295 self.vendordata_raw = md['vendor-data']
296 self.network_data = md['network-data']
297 self.routes_data = md['routes']
298
299 self._set_provisioned()
300 return True
301
302 def device_name_to_device(self, name):
303 return self.ds_cfg['disk_aliases'].get(name)
304
305 def get_config_obj(self):
306 if self.smartos_type == SMARTOS_ENV_KVM:
307 return BUILTIN_CLOUD_CONFIG
308 return {}
309
310 def get_instance_id(self):
311 return self.metadata['instance-id']
312
313 @property
314 def network_config(self):
315 if self._network_config is None:
316 if self.network_data is not None:
317 self._network_config = (
318 convert_smartos_network_data(
319 network_data=self.network_data,
320 dns_servers=self.metadata['dns_servers'],
321 dns_domain=self.metadata['dns_domain'],
322 routes=self.routes_data))
323 return self._network_config
324
325
326 class JoyentMetadataFetchException(Exception):
327 pass
328
329
330 class JoyentMetadataTimeoutException(JoyentMetadataFetchException):
331 pass
332
333
334 class JoyentMetadataClient(object):
335 """
336 A client implementing v2 of the Joyent Metadata Protocol Specification.
337
338 The full specification can be found at
339 http://eng.joyent.com/mdata/protocol.html
340 """
341 line_regex = re.compile(
342 r'V2 (?P<length>\d+) (?P<checksum>[0-9a-f]+)'
774 # SDC LX-Brand Zones lack dmidecode (no /dev/mem) but
775 # report 'BrandZ virtual linux' as the kernel version
776 if uname_version is None:
777 uname_version = uname[3]
778 if uname_version.lower() == 'brandz virtual linux':
779 return SMARTOS_ENV_LX_BRAND
780
781 if product_name is None:
782 system_type = util.read_dmi_data("system-product-name")
783 else:
784 system_type = product_name
785
786 if system_type and 'smartdc' in system_type.lower():
787 return SMARTOS_ENV_KVM
788
789 return None
790
791
792 # Convert SMARTOS 'sdc:nics' data to network_config yaml
793 def convert_smartos_network_data(network_data=None,
794 dns_servers=None, dns_domain=None,
795 routes=None):
796 """Return a dictionary of network_config by parsing provided
797 SMARTOS sdc:nics configuration data
798
799 sdc:nics data is a dictionary of properties of a nic and the ip
800 configuration desired. Additional nic dictionaries are appended
801 to the list.
802
803 Converting the format is straightforward though it does include
804 duplicate information as well as data which appears to be relevant
805 to the hostOS rather than the guest.
806
807 For each entry in the nics list returned from query sdc:nics, we
808 create a type: physical entry, and extract the interface properties:
809 'mac' -> 'mac_address', 'mtu', 'interface' -> 'name'. The remaining
810 keys are related to ip configuration. For each ip in the 'ips' list
811 we create a subnet entry under 'subnets' pairing the ip to a one in
812 the 'gateways' list.
813 """
814
815 valid_keys = {
816 'physical': [
817 'mac_address',
818 'mtu',
819 'name',
820 'params',
821 'subnets',
822 'type',
823 ],
824 'subnet': [
825 'address',
826 'broadcast',
827 'dns_nameservers',
828 'dns_search',
829 'metric',
830 'pointopoint',
831 'routes',
832 'scope',
833 'type',
834 ],
835 'route': [
836 'destination',
837 'gateway',
838 ],
839 }
840
841 if dns_servers:
842 if not isinstance(dns_servers, (list, tuple)):
843 dns_servers = [dns_servers]
844 else:
845 dns_servers = []
846
847 if dns_domain:
848 if not isinstance(dns_domain, (list, tuple)):
849 dns_domain = [dns_domain]
850 else:
851 dns_domain = []
852
853 def is_valid_ipv4(addr):
854 return '.' in addr
855
856 def is_valid_ipv6(addr):
857 return ':' in addr
858
884 })
885
886 proto = 'ipv4' if is_valid_ipv4(ip) else 'ipv6'
887 # Only use gateways for 'primary' nics
888 if 'primary' in nic and nic.get('primary', False):
889 # the ips and gateways list may be N to M, here
890 # we map the ip index into the gateways list,
891 # and handle the case that we could have more ips
892 # than gateways. we only consume the first gateway
893 if not pgws[proto]['gw']:
894 gateways = [gw for gw in nic.get('gateways', [])
895 if pgws[proto]['match'](gw)]
896 if len(gateways):
897 pgws[proto]['gw'] = gateways[0]
898 subnet.update({'gateway': pgws[proto]['gw']})
899
900 subnets.append(subnet)
901 cfg.update({'subnets': subnets})
902 config.append(cfg)
903
904 if routes:
905 for route in routes:
906 cfg = dict((k, v) for k, v in route.items()
907 if k in valid_keys['route'])
908 # Linux uses the value of 'gateway' to determine automatically if
909 # the route is a forward/next-hop (non-local IP for gateway) or an
910 # interface/resolver (local IP for gateway). So we can ignore
911 # the 'interface' attribute of sdc:routes, because SDC guarantees
912 # that the gateway is a local IP for "interface=true".
913 cfg.update({
914 'type': 'route',
915 'destination': route['destination'],
916 'gateway': route['gateway']})
917 config.append(cfg)
918
919 if dns_servers:
920 config.append(
921 {'type': 'nameserver', 'address': dns_servers,
922 'search': dns_domain})
923
924 return {'version': 1, 'config': config}
925
926
927 # Used to match classes to dependencies
928 datasources = [
929 (DataSourceSmartOS, (sources.DEP_FILESYSTEM, )),
930 ]
931
932
933 # Return a list of data sources that match this set of dependencies
934 def get_datasource_list(depends):
935 return sources.list_from_depends(depends, datasources)
936
937
938 if __name__ == "__main__":
939 import sys
940 jmc = jmc_client_factory()
941 if jmc is None:
942 print("Do not appear to be on smartos.")
943 sys.exit(1)
944 if len(sys.argv) == 1:
945 keys = (list(SMARTOS_ATTRIB_JSON.keys()) +
946 list(SMARTOS_ATTRIB_MAP.keys()) + ['network_config'])
947 else:
948 keys = sys.argv[1:]
949
950 def load_key(client, key, data):
951 if key in data:
952 return data[key]
953
954 if key in SMARTOS_ATTRIB_JSON:
955 keyname = SMARTOS_ATTRIB_JSON[key]
956 data[key] = client.get_json(keyname)
957 elif key == "network_config":
958 for depkey in ('network-data', 'dns_servers', 'dns_domain', 'routes'):
959 load_key(client, depkey, data)
960 data[key] = convert_smartos_network_data(
961 network_data=data['network-data'],
962 dns_servers=data['dns_servers'],
963 dns_domain=data['dns_domain'],
964 routes=data['routes'])
965 else:
966 if key in SMARTOS_ATTRIB_MAP:
967 keyname, strip = SMARTOS_ATTRIB_MAP[key]
968 else:
969 keyname, strip = (key, False)
970 data[key] = client.get(keyname, strip=strip)
971
972 return data[key]
973
974 data = {}
975 for key in keys:
976 load_key(client=jmc, key=key, data=data)
977
978 print(json.dumps(data, indent=1, sort_keys=True,
979 separators=(',', ': ')))
980
981 # vi: ts=4 expandtab
|