Print this page
Handle routes_data better
Handle 'routes' being empty (missing bits)
Handle 'routes' being empty
IMAGE-1097 cloud-init doesn't use sdc:routes metadata


 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