Nueva publicación

查找

Pregunta
· 1 hr atrás

Persistent Python DB-API Connection Issues (SSL Error) to IRIS CE Docker despite SSL disabled

Hello,

I'm trying to connect a Python backend application to an InterSystems IRIS Community Edition instance running in a Docker container on an AWS EC2 instance. I'm facing persistent connection issues and an SSL Error despite the Superserver apparently having SSL disabled. I'm hoping for some insight into what might be causing this contradictory behavior.

My Setup:

  • InterSystems IRIS: Community Edition (Docker image intersystems/iris-community:2025.1)
  • Deployment: AWS EC2 (Ubuntu) instance.
  • Port Mapping: Host port 9091 mapped to container port 1972 (Superserver). Host port 9092 mapped to container port 52773 (Management Portal).
  • Persistent Storage: Configured and confirmed working with irisowner user and appropriate permissions.
  • Python Client: Using the intersystems_irispython package (version 5.1.2) as the DB-API driver.

Symptoms and Diagnostics Performed:

  1. Python Connection Error:
    • My Python script attempts to connect using iris.connect().
    • The error received is: RuntimeError: <COMMUNICATION LINK ERROR> Failed to connect to server; Details: <SSL Error>.
  2. telnet Test to Superserver Port:
    • From the Python backend's EC2 instance, I ran telnet YOUR_EC2_PUBLIC_IP 9091.
    • The output shows: Connected to ... followed immediately by Connection closed by foreign host.
    • This indicates the TCP connection is established, but the server immediately drops it.
  3. AWS Security Group Check:
    • Inbound rules for the IRIS EC2 instance explicitly allow TCP traffic on port 9091 from 0.0.0.0/0 (for testing, will restrict later).
    • Outbound rules from the backend EC2 instance allow all traffic.
    • Conclusion: Basic network/firewall is not blocking the connection.
  4. InterSystems IRIS Management Portal (Superserver SSL Configuration):
    • I accessed System Administration > Security > Superservers > Edit Superserver 1972.
    • Under "SSL/TLS support level", the "Disabled" radio button is selected. This confirms, according to the portal, that the Superserver is NOT configured for SSL.

The Contradiction:

The primary source of confusion is that both the Python client and the telnet behavior suggest the server is expecting an SSL connection (or immediately rejecting non-SSL), despite the Management Portal explicitly showing "SSL/TLS support level: Disabled" for Superserver 1972.

Actions Taken (Python Script Variations):

  • Attempted iris.connect with no sslconfig parameter (default).
  • Attempted iris.connect with sslconfig=False.
  • Attempted iris.connect with an ssl.SSLContext object (received sslconfig must be a string or bool error, indicating this parameter expects specific types).

My Question:

Given that the Management Portal indicates SSL is disabled for the Superserver, what could be causing the persistent <SSL Error> from the Python client and the immediate Connection closed by foreign host from telnet? Are there any other hidden configurations or common pitfalls that could lead to this behavior?

Any help or insights would be greatly appreciated!

1 nuevo comentario
Comentarios (1)1
Inicie sesión o regístrese para continuar
Artículo
· 3 hr atrás Lectura de 16 min

The Zen Angle

Dear community, I have a confession to make. I have not gotten over Zen yet. Alas, all good things must come to an EOF, so I am currently learning about Angular. I am working on proving to myself that with the right back end and Angular components, I can deliver to myself and my team a very Zen-like experience in this environment. Since this is my first attempt, here is a fair warning: I will be providing some rather large code samples before discussing them. Please warm up your mouse and hand for extensive upcoming scrolling! Also, a note on the code snippets: many of them are labeled "Javascript" when they should actually be "Typescript"; Typescript simply is not an option when inserting a code snippet.

The Back End

As with most front ends, I will need a REST API to call. If you are familiar with how the %CSP.REST class extends, you should be very comfortable with the class below.

Class DH.REST Extends %CSP.REST
{
    Parameter HandleCorsRequest = 1;
    XData UrlMap [ XMLNamespace = "http://www.intersystems.com" ]
    {
        <Routes>
            <Route Url="/select/:class/:id" Method="GET" Call="Select" />
            <Route Url="/update/:class/:id" Method="PUT" Call="Update" />
            <Route Url="/delete/:class/:id" Method="DELETE" Call="Delete" />
            <Route Url="/insert/:class" Method="POST" Call="Update" />
        </Routes>
    }
    ClassMethod Select(class, id) As %Status
    {
        try{
            set myobj = $CLASSMETHOD(class,"%OpenId",id,,.sc)
            $$$ThrowOnError(sc)
            do myobj.%JSONExport()
            return $$$OK
        }
        catch ex{
            return ex.AsStatus()
        }
    }
    ClassMethod Delete(class, id) As %Status
    {
        try{
            return $CLASSMETHOD(class,"%DeleteId",id)
        }
        catch ex{
            return ex.AsStatus()
        }
    }
    ClassMethod Update(class, id = 0) As %Status
    {
        try{
            set myobj = ##class(%Library.DynamicObject).%FromJSON(%request.Content)
            if %request.Method = "POST"{
                set record = $CLASSMETHOD(class,"%New")
            }
            else{
                set record = $CLASSMETHOD(class,"%OpenId",id,,.sc)
                $$$ThrowOnError(sc)
            }
            $$$ThrowOnError(record.%JSONImport(%request.Content))
            $$$ThrowOnError(record.%Save())
            do record.%JSONExport()
            return $$$OK
        }
        catch ex{
            return ex.AsStatus() 
        }
    }
}

Most of this is basic IRIS stuff. I have a route map with routes defined to select, update, and delete records. I have the methods defined to follow that. Those methods are generally usable for any persistent class that extends the %JSON.Adaptor through the use of $CLASSMETHOD. In the update method, I utilize the %JSON.Adaptor’s %JSONImport method.  Also, note that I have written this method in a way that allows it to handle both creating new records and updating existing ones, eliminating redundant code. To accomplish it, I have defined two different routes and checked the %request.Method to determine whether to open an existing record or create a new one.

I have this class set as a dispatch class for a web application called /zenular. Since I am only testing the concept on my local PC and not planning to go live, I am allowing unauthenticated access for simplicity’s sake. Of course, in a production environment, you would prefer to secure the API and limit the tables it can work on. However, it is a discussion for another article.

For my proof-of-concept purposes today, I will be using a very simple persistent class called DH.Person. It will contain just three properties: a first name, a last name, and a calculated MyId field, which will represent the object's ID. We will use it (MyId) when creating new objects. Without it, when we ask the API to save a new object, the JSON we return would not include the newly assigned ID, which we will need for our form. It will extend the %JSON.Adaptor class, as my API utilizes it.

Class DH.Person Extends (%Persistent, %JSON.Adaptor)
{
    Property FirstName As %String;
    Property LastName As %String;
    Property MyId As %Integer [ Calculated, SqlComputeCode = { set {*}={%%ID}}, SqlComputed ];
}

The Project

In my Angular project directory, I have created a new project called “zenular” using the command below:

ng new zenular

It generates numerous default files and folders for the project. Within the app subfolder, I have made another subfolder, also named "zenular," to house all my Angular bits and pieces. Currently, I will only be modifying one of those files (app.config.ts):

import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core';
import { provideRouter } from '@angular/router';
import { provideHttpClient } from '@angular/common/http';
import { routes } from './app.routes';
export const appConfig: ApplicationConfig = {
  providers: [provideZoneChangeDetection({ eventCoalescing: true }), provideRouter(routes),provideHttpClient()]
};

Note that I have added an import for provideHttpClient as well as provideHttpClient() to the providers here. I did it to make the HttpClient class available and ready to go throughout our other components. Since we are going to call a REST API, we will need to use it.

From the command line, within the /app/zenular project folder, I also ran the following commands to generate the pieces I will require:

ng generate service zervice
ng generate component ztext
ng generate component zformbutton
ng generate component zform

For each generated component, I get a folder with four files: a .component.css file, a .component.html file, a .component.ts file, and a .component.ts.spec file. However, we have just one file (zervice.ts) for our purposes. Therefore, we will perform most of our work in the .ts files.

zservice

import {Injectable} from '@angular/core';
import {BehaviorSubject} from 'rxjs';
@Injectable({providedIn: 'root',})

export class DataControl{
    private updatecount = new BehaviorSubject<number>(0);
    zform: string = "";
    zid: string = "";
    zop: string = "";
    currentcount = this.updatecount.asObservable();

    updateForm(zformid: string, zid: string, zop: string){
        this.zform = zformid;
        this.zid = zid;
        this.zop = zop;
        this.updatecount.next(this.updatecount.value + 1);
    }
}

This is the shortest and simplest part of the project. An Angular service provides a place for various Angular components to share data, meaning that when one of our components updates the zservice, those changes become visible to the other components. To enable components to watch for alterations, you require something observable. In this case, I have created a BehaviorSubject that is simply a number. My plan is to allow various components to utilize the updateForm function to notify forms on the page about the following: which form they are requesting a change on, a record ID if needed, and an operation to perform. After setting that, we increment our BehaviorSubject. The forms will detect it, check whether they are the intended target, and execute the requested operation accordingly.

ztext

Iplan to attempt using text fields for my proof of concept. Naturally, I could go for other inputs if I wanted, but this is a good place to start. I will be working within the .ts file.

import { Component, Attribute } from '@angular/core';
import { DataControl } from '../zservice'
@Component({
  selector: 'ztext',
  imports: [],
  template: '<input type="text" value="{{value}}" (change)="onChange($event)" />',
  styleUrl: './ztext.component.css'
})

export class ZtextComponent {
  value: string = ""
  fieldname: string = ""
  dcform: string = ""
  constructor(@Attribute('fieldname') myfieldname:string, @Attribute('dataControllerForm') mydcform:string, private dc:DataControl){
    this.fieldname = myfieldname
    this.dcform = mydcform
  }

  onChange(e: Event){
    const target = e.target as HTMLInputElement
    this.value = target.value
    if (this.dcform !== null){
      this.dc.updateForm(this.dcform,target.value,'select')
    }
  }
}

You might notice that I am importing the Attribute class from the @angular.core package. It will allow us to read the attributes of the HTML element that we will employ to put this component on a template later on. I have set the selector to ztext, meaning that I will utilize a <ztext /> HTML-style tag to operate this component. I have replaced the default templateUrl with a simple template here. I used (change) to ensure this component utilizes the onChange method, defined in this class, whenever the text box's value changes.

By default, there is an exported class ZtextComponent in the file. However, I have modified it by adding a few string values. One of them is for the value of the component. Another one is called fieldname, and I will use it to indicate which property of a persistent class in IRIS each ztext component corresponds to. I also want to allow a text box to control a form, so I have one more property called dcform that defines which form it will control. I intend to allow users to type a record ID into the text input and utilize the REST API to populate the remaining form with the relevant data.

The constructor is also crucial here. I have used the @Attribute decorator in the constructor definition to specify which attributes of the component’s tag will be used in the constructor to set the component’s properties. In this case, I have configured it to use the fieldname attribute to set the component's fieldname property and the dataControllerForm attribute to set the dcform for the component. I have also added a reference to the DataControl class from the zervice to give the class access to that service.

In the onChange event, we retrieve the value from the HTML text input. If this component is configured to be a data controller for a form, we then pass the form name, the value, and the string "select" to the zservice. When we decide to create the form itself later, we will ensure it watches for those requests and handles them accordingly. 

zformbutton

Being able to load data is important, but it is no fun if we cannot also save or delete it! We need buttons.

import { Component, Attribute } from '@angular/core';
import { DataControl } from '../zservice';

@Component({
  selector: 'zformbutton',
  imports: [],
  template: '<button (click)="onClick()"><ng-content></ng-content></button>',
  styleUrl: './zformbutton.component.css'
})

export class ZformbuttonComponent {
  dcform: string = "";
  dcop: string = "";

  constructor(@Attribute('dataControllerForm') mydcform:string, @Attribute('dataControllerOp') mydcop:string, private dc:DataControl){
    this.dcform = mydcform;
    this.dcop = mydcop;
  }

  onClick(){
    this.dc.updateForm(this.dcform,'',this.dcop);
  }
}

There are various similarities between this and the ztext. We use attributes to set the component’s dcform and dcop, which I will employ to define whether this button is supposed to update, delete, or create a new record. I have once more provided it with the DataControl class from zservice. I have also replaced the templateUrl with a simple template again. You should notice a matched set of ng-content tags in the middle of it. This is how you tell Angular where any content placed between the opening and closing selector tags for a component should go. We similarly have an event that utilizes the zservice’s updateForm method to request a data operation. This time, it is the click event instead of the change, and we pass the form name (not an ID) since the form will operate on its current record and component’s dcop.

The App Component

When I created my project, I got a default app component. I have modified the app.component.ts file as shown below:

import { Component } from '@angular/core';
import { RouterOutlet } from '@angular/router';
import { ZtextComponent } from './zenular/ztext/ztext.component';
import { ZformComponent } from './zenular/zform/zform.component';
import { ZformbuttonComponent } from './zenular/zformbutton/zformbutton.component';

@Component({
  selector: 'app-root',
  imports: [RouterOutlet, ZtextComponent, ZformComponent, ZformbuttonComponent],
  templateUrl: './app.component.html',
  styleUrl: './app.component.css',
})

export class AppComponent {
  title = 'zenular';
  apiroot = 'http://localhost:52773'
}

As of this moment, we have not yet discussed the zform. However, I am bringing up this app component now because I have included a property called apiroot within it. I have done this exclusively to make the project more portable. I am not entirely sure whether it will be necessary in the long run. Yet, it is important to keep that in mind as we move into the zform. Also, note the imports in this file, both at the top and in the @Component. It will be essential for using those components in the app.component.html file later on.

Zform

This is the component that will do the heavy lifting and interact with the API.

import { Component, OnDestroy, Attribute, ContentChildren, QueryList } from '@angular/core';
import { DataControl } from '../zservice';
import { Subscription } from 'rxjs';
import { ZtextComponent } from '../ztext/ztext.component';
import {HttpClient} from '@angular/common/http';
import { AppComponent } from '../../app.component';

@Component({
  selector: 'zform',
  imports: [],
  template: '<ng-content></ng-content>',
  styleUrl: './zform.component.css'
})

export class ZformComponent implements OnDestroy {
  subscription:Subscription;
  formname: string = "";
  currentId: string = "";
  table: string = "";
  @ContentChildren(ZtextComponent) contentChildren!: QueryList<any>;

  constructor(@Attribute('name') myname:string, @Attribute('tablename') mytable: string, private dc:DataControl, private http: HttpClient, private appcon: AppComponent){
    this.subscription = this.dc.currentcount.subscribe(count => (this.updateData()));
    this.table = mytable
    this.formname = myname
  }

  updateData(){
    if (this.formname === this.dc.zform){
      if(this.dc.zop === 'select'){
          this.currentId = this.dc.zid
          this.http.get(this.appcon.apiroot+'/zenular/select/'+this.table+'/'+this.dc.zid, {responseType: 'text'}).subscribe((json: string) => {this.reloadForm(json)})
        }
      }
      if(this.dc.zop === 'update'){
        var jsonData : { [ key: string ]: any} = {};
          this.contentChildren.forEach(child => {
            jsonData[child.fieldname] = child.value;
          })
        if(this.currentId == ""){
          var reqid = "0"
          this.http.post(this.appcon.apiroot+'/zenular/insert/'+this.table, jsonData, {responseType: 'text'}).subscribe((json: string) => {this.reloadForm(json)})
        }
        else{
          var reqid = this.currentId
          this.http.put(this.appcon.apiroot+'/zenular/update/'+this.table+'/'+reqid, jsonData, {responseType: 'text'}).subscribe((json: string) => {this.reloadForm(json)})
        }
      }
      if(this.dc.zop === 'delete'){
        this.http.delete(this.appcon.apiroot+'/zenular/delete/'+this.table+'/'+this.currentId, {responseType: 'text'}).subscribe((json: string) => console.log(json))
        this.contentChildren.forEach(child => {
            child.value="";
        })
        this.currentId="";
      }
      if(this.dc.zop === 'new'){
        this.currentId = "";
      }
    }

  reloadForm(json: string){
    var jsonData = JSON.parse(json);
    this.contentChildren.forEach(child => {
      if(child.dcform !== this.formname){
        child.value=jsonData[child.fieldname];
      }
      else{
        child.value=jsonData['MyId'];
      }
    })
    this.currentId=jsonData['MyId'];
  }

  ngOnDestroy(): void {
    this.subscription.unsubscribe();
  }

}

My import list is significantly longer this time, but I still need the component and attribute. From the same package, I also require OnDestroy for cleanup when the component is destroyed, ContentChildren to retrieve a list of components within this component's ng-content, and QueryList to iterate over those components. Ialso still need the DataControl and Subscription to subscribe to the Observable in the zservice to trigger updates. Since I will be programmatically working with the ztext components, I must interact with their class. As this is where I will make the API requests, HttpClient will be required. I am also importing the previously mentioned AppComponent solely to access the API root. I have kept the template very simple, containing only the ng-content tags.

The class includes a property called contentChildren, which will provide a list of all the components within the ng-content tags that are the ztext components. The class also has a subscription, which is where the @Observable in the zservice comes into play. In the constructor, I set up that subscription to subscribe to that Observable. When it gets triggered, the zform calls its private updateData method. The subscription is also the reason for implementing the OnDestroy. I have defined an ngOnDestroy method that unsubscribes from the subscription. This is simply the best practice cleanup step.

The updateData form is where the interaction between the server and the application occurs. As you can see, the form first checks the zservice to determine whether it is the form whose update has been requested. If the operation is "new," we simply clear the form's currentId. Depending on your usage, you might also want to clear the entire form. For my particular implementation, we do not typically empty the form to create a new record. Since we are an ERP system, it is common to make multiple records for such things as item numbers, where the majority of fields are identical, with only three or four different ones. This is why we leave them populated to facilitate the quick production of similar records. However, your use case may demand something else.

If the operation is "delete", we send a request to the delete API to remove the current record. We also clear all form inputs and the form's currentId. 

If the request is "select", we check the zservice for the ID we are looking for, then send that request to the API. Upon receiving the response, we iterate over the inputs and pull values for them out of the JSON according to their fieldname properties.

If it is an "update", I package the values of all text inputs into JSON and apply a "put" request to the API with the help of the currentId. Alternatively, I utilize a "post" request without the ID if the currentId is not set, since this is how my API knows it should make a new record. The API responds with the JSON of the new object, which I then reload into the form. This is where the MyId field comes into play. When we create a new record, we need to pay attention to that and set the form’s currentId and data controller field. It will ensure that further changes to the object will result in an update rather than the creation of a new form.

Back to the App Component

When I previously discussed the app component, I left the templateUrl in the .ts file intact. This means that to add components to it, we should go to app.component.html. Now it is time to see how all that groundwork will pay off! We will replace the entire default contents of that file with the following:

<main class="main">
  <zform tablename="DH.Person" name="PersonForm">
    <ztext dataControllerForm="PersonForm" fieldname="ID" table="DH.Person"/>
    <ztext fieldname="FirstName" />
    <ztext fieldname="LastName" />
    <zformbutton dataControllerForm="PersonForm" dataControllerOp="update">Save</zformbutton>
    <zformbutton dataControllerForm="PersonForm" dataControllerOp="delete">Delete</zformbutton>
    <zformbutton dataControllerForm="PersonForm" dataControllerOp="new">New</zformbutton>
  </zform>
</main>

With just those steps, we have successfully created a basic form featuring fields that synchronize with IRIS via our API, complete with buttons to save and delete records. 

While this is still a bit rough, it has provided strong reassurance that this is a promising direction for us! If you would like to witness how this project continues to develop, let me know and suggest what you would like to see refined next. Also, since I have only been using Angular for a couple of weeks, please let me know what I could have done better. 

Comentarios (0)1
Inicie sesión o regístrese para continuar
Anuncio
· 9 hr atrás

InterSystems Developer Ecosystem News, Q2 2025

Hello and welcome to the Developer Ecosystem News!

The second quarter of the year was full of exciting activities in the InterSystems Developer Ecosystem. In case you missed something, we've prepared a selection of the hottest news and topics for you to catch up on!

News

🎇 Introducing Developer Community AI Bot

🎆 Chat with Developer Community AI!

💡 InterSystems Ideas News #21 and #22

🏌 Code Golf - Maximum Coverage

👨‍💻 Early Access Program: OAuth2 improvements

📝 Try the new AskMe learning chatbot!

📝 InterSystems IRIS Development Professional Exam is now LIVE!

📝 InterSystems Platforms Update Q2-2025

📝 June 10, 2025 – Advisory: Namespace Switching and Global Display Failures

📝 InterSystems announces InterSystems IRIS support for Red Hat Enterprise Linux 10

📝 InterSystems Cloud Services - Release Notes - 27 June 2025

📝 Alert: InterSystems IRIS 2024.3 – AIX JSON Parsing Issue & IntegratedML Incompatibilities

Contests & Events

 
Code Your Way to InterSystems READY 2025

📰 Fourth Spanish Technical Article Contest

🤝 Developer Meetup in Cambridge - GenAI & AI Agents

🤝 Cambridge Developer Meetup - AI Coding Assistants & MCP

🤝 FHIR France Meetup #13

📺 [Webinar] SQL Cloud Services

📺 [Webinar] Unlocking the Power of InterSystems Data Fabric Studio

📺 [Hebrew Webinar] Discover the All-New UI in Version 2025.1 — and More!

Latest Releases

⬇️ InterSystems API Manager (IAM) 3.10 Release Announcement

⬇️ IKO 3.8 Release

⬇️ New Point Release for IRIS 2025.1.0: Critical Interoperability Fix

⬇️ Point Releases Available to Address Namespace Switching and Global Display Issues in Recent 2025.1.0, 2024.1.4, 2023.1.6, and 2022.1.7 Versions

⬇️ Maintenance Releases 2024.1.4 and 2023.1.6 of InterSystems IRIS, IRIS for Health, & HealthShare HealthConnect are now available

Best Practices & Key Questions

❓ Key Questions: April, MayJune

People and Companies to Know About 

👩‍🏫 Celebrating a Lifelong Contributor of the Developer Community

💼 CACHÉ #Job opportunity

💼 InterSystems HealthShare developer interoperability Developer/Lead

💼 IRIS Data Platform Engineer

👨‍💻 InterSystems developer Job

So...

Here is our take on the most interesting and important things! 

What were your highlights from this past quarter? Share them in the comments section and let's remember the fun we've had!

Comentarios (0)1
Inicie sesión o regístrese para continuar
Job
· 13 hr atrás

Solution Architect est recherché

InterSystems recherche un Solution Architect pour accompagner sa croissance continue en Belgique, en se concentrant sur les engagements stratégiques dans le secteur public.

Présentation du poste :

En tant que Solution Architect, vous collaborerez avec les clients du secteur public afin de comprendre leurs besoins et de proposer des solutions techniques robustes et évolutives utilisant la technologie InterSystems. Vous jouerez un rôle clé dans la résolution des défis complexes liés à l'intégration, à l'analyse et à la conformité des données.

Responsabilités :

  • Agir en tant que conseiller technique principal auprès des clients et prospects en Belgique
  • Concevoir et présenter des solutions sur mesure et des démonstrations de produits
  • Diriger les évaluations techniques et les mises en œuvre de validations de principe
  • Collaborer avec les équipes commerciales, marketing et de développement produit
  • Guider les partenaires et les clients sur l'architecture, le déploiement, la sécurité et les meilleures pratiques opérationnelles
  • Animer des ateliers techniques et des sessions de formation
  • Assister aux réponses aux appels d'offres et à la documentation technique des marchés publics

Qualifications :

  • Plus de 8 ans d'expérience dans les plateformes de données, l'intégration de systèmes ou l'analyse d'entreprise
  • Expérience avérée dans des projets informatiques du secteur public ou en collaboration directe avec des clients gouvernementaux
  • Solide compréhension de la conception et de l'intégration d'API, notamment des services REST et SOAP
  • Expérience en conception de bases de données relationnelles, notamment en optimisation de schémas et en performances des requêtes
  • Maîtrise des stratégies de haute disponibilité (HA) et de basculement en environnement de production
  • Bonne compréhension des environnements informatiques d'entreprise, notamment en matière de sécurité, de conformité et d'évolutivité
  • Parler couramment le flamand, le français et l'anglais
  • Excellentes compétences en communication écrite et orale, et capacité à interagir avec des parties prenantes techniques et non techniques

>> appliquer ici <<

Comentarios (0)0
Inicie sesión o regístrese para continuar
Pregunta
· 15 hr atrás

Display date format in DD-MM-YYYY

Hello my friends,

I have a bit problem with date format, I need to display like this DD-MM-YYYY

I've put the objectscript like this : set DOB=$zd(paper.PAPERDob,15)  

and the result is 

Why It cannot show the full year, I mean why 99 or 95 why not 1999 or 1995 ? 

Thank You

Best Regards,

Steven Henry

5 nuevos comentarios
Comentarios (5)3
Inicie sesión o regístrese para continuar