/**
 * Copyright 2020 Opstrace, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */ import { delay , call } from "redux-saga/effects" ; import { ApiError as GCPApiError } from "@google-cloud/common" ; import Compute from "@google-cloud/compute" ; import { die , log , SECOND } from "@opstrace/utils" ; const doesSubNetworkExist = async ( client , { name } ) : Promise < unknown > => { return new Promise ( ( resolve , reject ) => { client . getSubnetworks ( ) . then ( ( data ) => { if ( ! data || ! Array . isArray ( data ) || ! data [ 0 ] ) { reject ( "Unexpected data structure returned for getSubnets" ) ; } resolve ( data [ 0 ] . find ( ( n ) => { const networkName = n . metadata . network . split ( "/" ) . pop ( ) ; return networkName === name ; } ) ) ; } ) . catch ( ( err ) => { reject ( err ) ; } ) ; } ) ; } ; const createSubNetwork = ( client , region , network , cidr , name ) => { return client . region ( region ) . createSubnetwork ( name , { network , range : cidr , privateIpGoogleAccess : true } ) ; } ; const deleteSubNetwork = ( client , region , name ) : Promise < unknown > => { log . debug ( "emit DELETE api call for subnetwork %s" , name ) ; return new Promise ( ( resolve , reject ) => { client . region ( region ) . subnetwork ( name ) . delete ( ( err , operation ) => { if ( err ) { reject ( err ) ; } resolve ( operation ) ; } ) ; } ) ; } ; export interface SubnetRequest { opstraceClusterName : string ; gcpRegion : string ; gcpProjectID : string ; ipCidrRange : string ; } export function * ensureSubNetworkExists ( { opstraceClusterName , gcpRegion , gcpProjectID , ipCidrRange } ) { const client = new Compute ( ) ; const snetname = opstraceClusterName ; const region = gcpRegion ; while ( true ) { const existingSubNetwork = yield call ( doesSubNetworkExist , client , { name : snetname } ) ; let creationInitiated = false ; if ( existingSubNetwork ) { log . info ( "subnetwork found to exist, done" ) ; break ; } else { if ( creationInitiated ) { yield delay ( 1 * SECOND ) ; continue ; } try { yield call ( createSubNetwork , client , region , `projects/${ gcpProjectID }/global/networks/${ snetname }` , ipCidrRange , snetname ) ; } catch ( e ) { if ( e instanceof GCPApiError ) { log . info ( "test for JP: e instanceof GCPApiError: %s" , JSON . stringify ( e , null , 2 ) ) ; } if ( e . code === 400 ) { if ( e . message && e . message . includes ( "not yet ready" ) ) { log . info ( "retry in 5 s (%s)" , e . message ) ; yield delay ( 5 * SECOND ) ; continue ; } } if ( e . code === 409 ) { log . info ( "subnetwork created before, done" ) ; break ; } throw e ; } creationInitiated = true ; } } } export function * ensureSubNetworkDoesNotExist ( opstraceClusterName , gcpRegion ) { const client = new Compute ( ) ; let operation ; let error = null ; const snetname = opstraceClusterName ; const region = gcpRegion ; while ( true ) { log . debug ( "check if subnetwork exists" ) ; const existingSubNetwork = yield call ( doesSubNetworkExist , client , { name : snetname } ) ; if ( ! existingSubNetwork ) { log . info ( "subnetwork does not exist, leave" ) ; return ; } log . debug ( "existingSubNetwork: %s" , existingSubNetwork ) ; if ( existingSubNetwork . region . name !== gcpRegion ) { die ( `existingSubNetwork.region.name: ${ existingSubNetwork . region . name } ` + `vs. region to destroy in: ${ gcpRegion }` ) ; } if ( error ) { throw error ; } if ( operation ) { log . info ( "subnetwork deletion operation was initiated, wait for subnetwork to disappear" ) ; yield delay ( 10 * SECOND ) ; continue ; } try { operation = yield call ( deleteSubNetwork , client , region , snetname ) ; log . debug ( "subnetwork deletion: operation: %s" , operation ) ; operation . on ( "complete" , ( metadata ) => { operation . removeAllListeners ( ) ; operation = null ; log . info ( `Subnet deletion is: ${ metadata . status } with ${ metadata . progress } % progress` ) ; } ) ; operation . on ( "running" , ( metadata ) => { log . info ( `Subnet deletion has started with status: ${ metadata . status }` ) ; } ) ; operation . on ( "error" , ( err ) => { operation . removeAllListeners ( ) ; operation = null ; error = err ; } ) ; } catch ( e ) { if ( e instanceof GCPApiError ) { log . info ( "test for JP: e instanceof GCPApiError: %s" , JSON . stringify ( e , null , 2 ) ) ; } if ( ! e . code || ( e . code && e . code !== 404 ) ) { throw e ; } log . debug ( "error during subnetwork deletion: %s" , e ) ; } yield delay ( 5 * SECOND ) ; } }