55from unicodedata import name # enforce a timeout; sleep
66from uuid import UUID # validate UUIDv4 strings
77
8- from .utility import (EXCLUDED_PATCH_PROPERTIES , HOST_PROPERTIES ,
8+ from .utility import (DC_PROVIDERS , EXCLUDED_PATCH_PROPERTIES , HOST_PROPERTIES ,
99 MAJOR_REGIONS , RESOURCES , STATUS_CODES , VALID_SEPARATORS ,
1010 VALID_SERVICE_PROTOCOLS , docstring_parameters , eprint ,
1111 http , plural , singular )
@@ -165,7 +165,7 @@ def validate_entity_roles(self, entities: list, type: str):
165165 an ERP.
166166
167167 :param list entities: required hashtag role attributes, existing entity @names, or existing entity UUIDs
168- :param str type: required type of entity: one of {resource_entity_types}
168+ :param str type: required type of entity, choices: {resource_entity_types}
169169 """
170170 valid_entities = list ()
171171 for entity in entities :
@@ -197,8 +197,47 @@ def validate_entity_roles(self, entities: list, type: str):
197197 else : valid_entities .append ('@' + entity_name ) # is an existing endpoint's name resolved from UUID
198198 return (valid_entities )
199199
200- def get_edge_router_data_centers (self ,provider : str = None ,location_code : str = None ):
201- """list the data centers where an Edge Router may be created
200+ def get_data_center_by_id (self , id : str ):
201+ """Get data centers by UUIDv4.
202+
203+ :param id: required UUIDv4 of data center
204+ """
205+ try :
206+ # data centers returns a list of dicts (data centers)
207+ headers = { "authorization" : "Bearer " + self .session .token }
208+
209+ response = http .get (
210+ self .session .audience + 'core/v2/data-centers/' + id ,
211+ proxies = self .session .proxies ,
212+ verify = self .session .verify ,
213+ headers = headers
214+ )
215+ response_code = response .status_code
216+ except :
217+ raise
218+
219+ if response_code == STATUS_CODES .codes .OK : # HTTP 200
220+ try :
221+ data_center = json .loads (response .text )
222+ except ValueError as e :
223+ eprint ('ERROR getting data center' )
224+ raise (e )
225+ else :
226+ raise Exception (
227+ 'ERROR: got unexpected HTTP code {:s} ({:d}) and response {:s}' .format (
228+ STATUS_CODES ._codes [response_code ][0 ].upper (),
229+ response_code ,
230+ response .text
231+ )
232+ )
233+ return (data_center )
234+
235+ @docstring_parameters (providers = str (DC_PROVIDERS ))
236+ def get_edge_router_data_centers (self , provider : str = None , location_code : str = None ):
237+ """Find data centers for hosting edge routers.
238+
239+ :param provider: optionally filter by data center provider, choices: {providers}
240+ :param location_code: provider-specific string identifying the data center location e.g. us-west-1
202241 """
203242 try :
204243 # data centers returns a list of dicts (data centers)
@@ -208,10 +247,10 @@ def get_edge_router_data_centers(self,provider: str=None,location_code: str=None
208247 "hostType" : "ER"
209248 }
210249 if provider is not None :
211- if provider in [ "AWS" , "AZURE" , "GCP" , "OCP" ] :
250+ if provider in DC_PROVIDERS :
212251 params ['provider' ] = provider
213252 else :
214- raise Exception ("ERROR: unexpected cloud provider {:s}" .format (provider ))
253+ raise Exception ("ERROR: unexpected cloud provider {:s}. Need one of {:s} " .format (provider , str ( DC_PROVIDERS ) ))
215254 response = http .get (
216255 self .session .audience + 'core/v2/data-centers' ,
217256 proxies = self .session .proxies ,
@@ -377,6 +416,8 @@ def get_resources(self, type: str,name: str=None, accept: str=None, deleted: boo
377416 params ['name' ] = name
378417 if typeId is not None :
379418 params ['typeId' ] = typeId
419+ if deleted :
420+ params ['status' ] = "DELETED"
380421
381422 if not type in RESOURCES .keys ():
382423 raise Exception ("ERROR: unknown type \" {}\" . Choices: {}" .format (type , RESOURCES .keys ()))
@@ -452,7 +493,9 @@ def get_resources(self, type: str,name: str=None, accept: str=None, deleted: boo
452493 )
453494 )
454495
455- # omit deleted entities by default
496+ # prune entities with non null deletedAt unless return deleted is true
497+ # TODO: remove this because the API has been changed to stop returning
498+ # deleted entities unless query param status=DELETED
456499 if not deleted :
457500 all_entities = [entity for entity in all_entities if not entity ['deletedAt' ]]
458501
@@ -828,14 +871,22 @@ def create_endpoint(self, name: str, attributes: list=[], session_identity: str=
828871 raise Exception ("ERROR: timed out waiting for process status 'STARTED' or 'FINISHED'" )
829872 return (started )
830873
874+ @docstring_parameters (providers = str (DC_PROVIDERS ))
831875 def create_edge_router (self , name : str , attributes : list = [], link_listener : bool = False , data_center_id : str = None ,
832- tunneler_enabled : bool = False , wait : int = 900 , sleep : int = 10 , progress : bool = False ):
876+ tunneler_enabled : bool = False , wait : int = 900 , sleep : int = 10 , progress : bool = False ,
877+ provider : str = None , location_code : str = None ):
833878 """Create an Edge Router.
834879
880+ A router may be hosted by NetFoundry or the customer. If hosted by NF,
881+ then you must supply datacenter "provider" and "location_code". If
882+ neither are given then the router is customer hosted.
883+
835884 :param name: a meaningful, unique name
836885 :param attributes: a list of hashtag role attributes
837- :param link_listener: true if router should listen for other routers' links on 80/tcp
838- :param data_center_id: the UUIDv4 of a data center location that can host edge routers
886+ :param link_listener: true if router should listen for other routers' transit links on 80/tcp, always true if hosted by NetFoundry
887+ :param data_center_id: (DEPRECATED by provider, location_code) the UUIDv4 of a NetFoundry data center location that can host edge routers
888+ :param provider: datacenter provider, choices: {providers}
889+ :param location_code: provider-specific string identifying the datacenter location e.g. us-west-1
839890 :param tunneler_enabled: true if the built-in tunneler features should be enabled for hosting or interception or both
840891 :param wait: seconds to wait for async create to succeed
841892 """
@@ -854,8 +905,25 @@ def create_edge_router(self, name: str, attributes: list=[], link_listener: bool
854905 "tunnelerEnabled" : tunneler_enabled
855906 }
856907 if data_center_id :
857- body ['dataCenterId' ] = data_center_id
908+ eprint ('WARN: data_center_id is deprecated by provider, location_code. ' )
909+ data_center = self .get_data_center_by_id (id = data_center_id )
910+ body ['provider' ] = data_center ['provider' ]
911+ body ['locationCode' ] = data_center ['locationCode' ]
858912 body ['linkListener' ] = True
913+ elif provider or location_code :
914+ if provider and location_code :
915+ data_centers = self .get_edge_router_data_centers (provider = provider , location_code = location_code )
916+ if len (data_centers ) == 1 :
917+ body ['provider' ] = provider
918+ body ['locationCode' ] = location_code
919+ body ['linkListener' ] = True
920+ else :
921+ raise Exception ("ERROR: failed to find exactly one {provider} data center with location_code={location_code}" .format (
922+ provider = provider ,
923+ location_code = location_code ))
924+ else :
925+ raise Exception ("ERROR: need both provider and location_code to create a hosted router." )
926+
859927 response = http .post (
860928 self .session .audience + 'core/v2/edge-routers' ,
861929 proxies = self .session .proxies ,
@@ -990,7 +1058,7 @@ def create_service_simple(self, name: str, client_host_name: str, client_port: i
9901058 :param: client_port is required integer of the ports to intercept
9911059 :param: server_host_name is optional string that is a hostname (DNS) or IPv4. If omitted service is assumed to be SDK-hosted (not Tunneler or Router-hosted).
9921060 :param: server_port is optional integer of the server port. If omitted the client port is used unless SDK-hosted.
993- :param: server_protocol is optional string of the server protocol.
1061+ :param: server_protocol is optional string of the server protocol, choices: {valid_service_protocols}. Default is ["tcp"] .
9941062 :param: attributes is optional list of strings of service roles to assign. Default is [].
9951063 :param: edge_router_attributes is optional list of strings of Router roles or Router names that can "see" this service. Default is ["#all"].
9961064 :param: egress_router_id is optional string of UUID or name of hosting Router. Selects Router-hosting strategy.
@@ -1901,15 +1969,15 @@ def get_network_by_id(self,network_id):
19011969 return (network )
19021970
19031971 def wait_for_property_defined (self , property_name : str , property_type : object = str , entity_type : str = "network" , wait : int = 60 , sleep : int = 3 , id : str = None , progress : bool = False ):
1904- """continuously poll until expiry for the expected property to become defined with the any value of the expected type
1972+ """Poll until expiry for the expected property to become defined with the any value of the expected type.
1973+
19051974 :param: property_name a top-level property to wait for e.g. `zitiId`
19061975 :param: property_type optional Python instance type to expect for the value of property_name
19071976 :param: id the UUID of the entity having a status if entity is not a network
19081977 :param: entity_type optional type of entity e.g. network (default), endpoint, service, edge-router
19091978 :param: wait optional SECONDS after which to raise an exception defaults to five minutes (300)
19101979 :param: sleep SECONDS polling interval
19111980 """
1912-
19131981 # use the id of this instance's Network unless another one is specified
19141982 if entity_type == "network" and not id :
19151983 id = self .id
@@ -1983,7 +2051,7 @@ def wait_for_entity_name_exists(self, entity_name: str, entity_type: str, wait:
19832051 """Continuously poll until expiry for the expected entity name to exist.
19842052
19852053 :param: entity_name
1986- :param: entity_type is singular or plural form, any of {resource_entity_types}
2054+ :param: entity_type is singular or plural form, choices: {resource_entity_types}
19872055 :param: wait optional SECONDS after which to raise an exception defaults to five minutes (300)
19882056 :param: sleep SECONDS polling interval
19892057 :param: progress print a horizontal progress meter as dots, default false
0 commit comments